---
title: 'Introduction to Version Control'
source: 'https://academia.sh/en/courses/introduction-to-version-control'
language: en
updated: '2026-08-17T04:42:50+00:00'
license: 'CC BY-SA 4.0'
lessons: 15
outcomes:
    - 'Separates the concepts of repository, working area, staging area and commit'
    - 'Reads the history of changes and examines the difference between two versions'
    - 'Undoes unwanted changes safely'
---

# Introduction to Version Control

A beginner course that starts from the repository's object model to build the working directory, the staging area and the commit cycle; it teaches reading history and safely undoing unwanted changes.

## The Idea of Version Control

The problem version control solves, the difference between centralized and distributed designs, and the creation and configuration of a repository.

### What Is Version Control

Source: https://academia.sh/en/courses/introduction-to-version-control/what-is-version-control

The problem version control solves; the promises of history, reversibility, and collaboration; and a comparison of snapshot-based and diff-based storage.

The Programming Fundamentals course covered how a program is written; this course is
concerned with how the written program changes over time. A file is not written once
and set aside: it is corrected, extended, broken, reverted. Working without recording
this sequence of changes produces a program whose only known state is its last one.

This lesson defines the problem version control solves and separates the three
promises the solution carries. Commands begin in the next topic; the model built here
is the vocabulary the rest of the course will use.

#### Manual Version Management and Its Limits

The most direct way to keep records is copying: a version of the file is backed up
before work begins, and the next version is saved under a separate name. The result is
a directory that looks like this:

```
search.sh
search-backup.sh
search-working.sh
search-final.sh
search-final-2.sh
```

This scheme has four separate flaws, and all four are structural:

1. **There is no ordering information.** File names do not say which version was
   derived from which. The relationship between `search-final.sh` and
   `search-working.sh` exists only in the author's memory.
2. **There is no record of rationale.** Why a line changed is written nowhere.
   Someone looking at the same line six months later cannot tell whether the change
   was a fix or an experiment.
3. **There is no change integrity.** A fix often touches more than one file. Copying
   file by file does not record at which moment these files were consistent together.
4. **Concurrent work is undefined.** If two people change copies of the same file
   separately, there is no rule for merging the two copies; one overwrites the other.

A **version control system** is a recording scheme designed to remove these four
flaws. It stores changes not file by file, but in wholes called **commits**; it
attaches a rationale text, an author, and a predecessor to every commit.

#### Three Promises

The guarantees version control offers fall under three headings.

**History.** Every commit represents the project's complete state at that moment and
links to the commit before it. This chain of links makes the question "when was this
line written, by whom, and for what reason" answerable. A significant part of reading
code is spent answering this question.

**Reversibility.** Every recorded state can be retrieved again. This makes bold
changes cheap: the cost of an experiment is the cost of undoing it. When the cost is
low, the number of experiments rises. The course's third topic is devoted to these
undo tools.

**Collaboration.** More than one person working on the same project requires changes
to be merged. Because a version control system knows which common ancestor each
change was derived from, it turns merging into a rule-governed operation. In this
course, collaboration will appear only as the rationale for design decisions; its
practice is the subject of the Branching and Collaboration course.

#### Snapshot or Diff

How a system stores history determines the operations it can offer. There are two
basic designs.

**Diff-based storage.** The system stores the first version in full; for every
version after that, it records only the difference from the previous version. This is
economical in storage. Its cost is that obtaining any version means starting from the
beginning and applying the diffs in order: the cost of reaching version $k$ is $O(k)$.

**Snapshot-based storage.** The system stores an image of the project's entire file
tree at every recorded moment. Reaching a version means looking directly at that
image: $O(1)$. At first glance it looks wasteful — will every file be rewritten at
every record?

What removes the waste is the idea of **content-addressed storage**. Every file's
content is named by a hash value computed from that content, and stored under that
name. The same content is never stored twice; unchanged files share the same object
across consecutive snapshots. The Data Structures course examined hash functions'
avalanche effect and collision probability; here, the same functions are used as an
identity generator.

This way, the snapshot model largely keeps the diff model's storage advantage while
bringing access to a version down to constant cost. The system this course covers —
`git` — uses the snapshot model.

#### The Shape of History

History is not a simple list. Every commit links to a predecessor commit; a commit
can have more than one successor (two separate directions were tried from the same
point), and a commit can have more than one predecessor (two directions were merged).
The links point toward the past, and a commit cannot be its own ancestor.

These three properties say that history is a **directed acyclic graph**. This
structure was defined in the Data Structures course, and its topological ordering was
covered in the Algorithms course. The correspondences here are exact:

| Graph concept | Counterpart in history |
|---|---|
| Node | A commit |
| Edge | The link from a commit to its predecessor |
| Acyclicity | A commit cannot be its own ancestor |
| Topological order | Listing history in a readable order |
| Common ancestor | The commit where two directions diverged |

Listing a history "from newest to oldest" is one of the graph's topological orders.
In a history with no branching, this order is unique; if there is branching, more
than one valid order exists, and the system has to choose which one to show.

```
(A) ← (B) ← (C) ← (D)        unbranched history: a single chain

            ┌── (C) ← (D)
(A) ← (B) ←─┤                branched history: two directions
            └── (E)
```

The arrows point toward the past: every node points to its own predecessor. Once a
commit is written, its content does not change; history only grows from its ends.
This **immutability** is the key to understanding how the undo tools work, and it
will come up again in the third topic.

#### Not the Same Thing as Backup

The two schemes are confused because both promise "bringing back the old state"; the
promises they make are different.

**Backup** is kept to recover the state at one point in time. Its purpose is
preventing data loss; it does not record who changed what or why, and it offers no
rule for merging two people's work. A backup's value is in being able to give back
the **last** state at the moment it is needed.

**Version control** keeps the sequence of states and the rationale for every step.
Recovery is only a side effect; the information it actually produces is the change
itself.

The two do not substitute for each other. If a repository sits on only one machine,
losing that machine takes the history with it; using version control does not make
backup unnecessary. The next lesson will show how the distributed model spreads out
this risk.

The distinction also determines the question of **what gets recorded**. What enters a
repository is content written by a human and backed by a rationale: source files,
configuration, documents. Build output and downloaded dependencies are reproducible
from their sources, so there is no need for them to take up space in history; secrets,
on the other hand, must never enter history at all, because once they enter they are
permanent. The practice of this distinction is the subject of the Ignore Rules lesson.

#### The Course's Example Repository

A single example project will be used throughout the course: a small piece of work
named `sozluk`. It holds three files — a description document, a text file holding
terms line by line, and a shell script that searches within it. The project will be
set up from scratch, every lesson will advance history by one step, and the lessons
in the third topic will make deliberate mistakes in the same repository and undo them.

The example's smallness is deliberate: as the file count drops, it becomes possible
to examine the object database's content directly and verify the model by eye. The
course's goal is not to make you memorize a list of commands, but to build this
model; commands stick in memory to the extent that they can be derived from it.

#### Summary

- Keeping versions by manual copying loses ordering, rationale, change-integrity, and
  concurrency information.
- A version control system stores changes not per file, but in wholes called commits.
- There are three basic promises: history, reversibility, and collaboration.
- Diff-based storage is cheap in storage, snapshot-based storage is cheap in access;
  content-addressed storage lowers the second model's storage cost.
- History is a directed acyclic graph; nodes are commits, edges are predecessor
  links.
- Backup recovers the last state, version control keeps the sequence of states and
  their rationale; the two do not substitute for each other.

#### Next Step

What version control is has been defined; but the question of where history is kept
remains open. On a single server, or as a complete copy on every participant's
machine? This choice determines which operations the system can perform without a
network connection, and what is lost in the event of a failure. The next lesson will
compare these two designs and their consequences.

### Centralized and Distributed Models

Source: https://academia.sh/en/courses/introduction-to-version-control/centralized-and-distributed-models

The design consequences of keeping history on a single server versus in every copy; the reason a version's identity is produced from a content hash rather than an increasing number.

The previous lesson defined history as a directed acyclic graph but did not say where
this graph stands. Is it kept on a single server, or does every participant's machine
hold a complete copy? This question is not an implementation detail: the answer
determines which operations the system can perform without a network connection, how
version identities are produced, and what is lost at the moment of a failure.

#### The Centralized Model

In **centralized version control**, history has a single authoritative copy, and it
stands on a server. Participants' machines hold only a **working copy**: the state of
the files at a given version, plus a small record saying which version it was taken
from. History itself is not present locally.

In this scheme, every operation goes to the server:

- Writing a new commit requires connecting to the server.
- Seeing a past version is requested from the server.
- The difference between two versions is computed on the server, or the missing data
  is pulled from the server.

What is gained in return is a simple source of truth. The question "what is the
project's current state" has a single answer, and that answer is on the server.
Access control is also applied at a single point: write permission to a given
directory can be limited by the server.

#### The Distributed Model

In **distributed version control**, every copy is complete. When a repository is
taken, not just the latest version but the whole of history — every commit, every
snapshot — is written to the local machine. What the participant holds is not a
working copy but a full **repository**.

The result is a split in the set of operations:

| Operation | Centralized | Distributed |
|---|---|---|
| Writing a commit | Server required | Done locally |
| Listing history | Server required | Done locally |
| Diffing two versions | Server required | Done locally |
| Returning to a past version | Server required | Done locally |
| Taking someone else's work | Server required | Network required |
| Sharing your own work | Server required | Network required |

The network is required only in the last two rows. This split is why every lesson of
this course can be done without a network connection: the repository set up over the
course will stay local and be functional on its own. The **remote** concept is the
subject of the Branching and Collaboration course.

#### The Identity Problem

The two models' most instructive difference is in how a version is named.

In the centralized model, the server is the sole authority; it can number versions
with an increasing count. Version 8 comes after version 7, and there is no dispute
over who assigns this number. The number carries both identity and ordering
information: 8 is after 7.

There is no such authority in the distributed model. If two people, unaware of each
other, write a commit on the same predecessor, both would pick the same value as the
"next number." The result is two different commits carrying the same identity, and
history becomes inconsistent. An increasing number requires a central counter.

The solution is to derive identity not from an external counter but from the content
itself. A commit's identity is a hash value computed over all of the commit's
components — its snapshot, its predecessor's identity, its author, its time, its
message. This choice has three consequences:

1. **Producing an identity requires no coordination.** Everyone computes their own
   commit's identity locally; the collision probability reduces to the hash
   function's **collision resistance**.
2. **Identity is content's integrity check.** If a single byte of a commit changes,
   its identity changes too. Corruption in the repository does not stay silent.
3. **Identity carries no ordering information.** One hash value is not "after"
   another hash value. Order is read only from the predecessor links — that is, from
   the graph itself.

The third consequence is exactly what made the previous lesson's discussion of
topological order necessary: ordering history is done not by comparing identities,
but by walking the graph.

Deriving identity from content settles one more point: since the predecessor's
identity is part of the computation, changing a past commit changes the identity of
every commit after it. History is sealed to itself, back from its tip — a sealing
whose consequences appear again in the third topic's "amending a commit" and "reset"
lessons.

#### What Is Expected of the Hash Function

Deriving identity from content places a specific load on the hash function used. The
Data Structures course examined hash functions as a dictionary structure's
distribution tool; there, a collision was a performance problem solved by chaining or
open addressing. Here, there is no solution for a collision: if two
different pieces of content produce the same identity, the repository can put one in
place of the other.

For this reason, the expected property is not distribution but **collision
resistance**: it being computationally infeasible to find two different pieces of
content that produce the same identity. Ordinary checksums do not carry this
property; cryptographic hash functions are designed to carry it.

The property is time-dependent: once a method for producing collisions against a
hash function is found, repositories relying on that function need to be migrated to
a stronger one. The repository format carries a version field, and the object
identity's length depends on the format, precisely to make this migration possible. The identities shown in this course consist of forty hexadecimal
characters; in a repository using a different hash function the length differs, but
the model is the same.

#### Redundancy and Failure

In the centralized model, the server is a single point of failure. If the server is
unreachable, writes stop; if the server's data is lost and there is no backup,
history is lost. Because working copies hold only the latest version, the past cannot
be brought back.

In the distributed model, every copy is a full backup. Even if a central server is
used — and in practice it usually is — that server is a coordination point, not the
only copy. For history to be lost, every copy would have to be lost.

In return, the distributed model brings two costs:

- **Storage.** Every participant keeps the whole of history. In projects with a long
  lifetime and large files, this cost becomes noticeable; a large binary file that
  entered history stays in every copy even after it is later deleted.
- **Access control.** Since the whole of history is present in every copy, the
  restriction "only certain people should see this directory" cannot be enforced at
  the repository level. Control shifts to the point where sharing happens.

The practical consequence of the second point is that secrets must never enter a
repository. Even if a password is deleted after being committed, it stays in history
and has spread to every copy. The ignore-rules lesson takes up this subject again.

#### What the Model Choice Determines

The choice between the two models determines which operations the tool makes cheap.
In the distributed model, writing a commit is cheap because it requires no network;
because it is cheap, it is done often; because it is done often, every commit can be
small and single-purpose. The "atomic commit" habit covered in the next topic is a
direct consequence of this cost structure.

In the same way, since the whole of history is present locally, querying the past is
cheap. Finding which commit changed a given line involves no network latency; this
also turns looking at the past into an ordinary reading habit.

#### Summary

- In the centralized model, history is kept on a single server; participants hold
  only a working copy.
- In the distributed model, every copy contains the whole of history; the network is
  needed only for sharing.
- The centralized model can assign version numbers with an increasing count; the
  distributed model has no central counter for this.
- Distributed systems therefore derive identity from content; identity also provides
  an integrity check, but it carries no ordering information.
- The distributed model's costs are storage and the impossibility of enforcing access
  control at the repository level.

#### Next Step

The model's vocabulary is complete. The next step is to build this model's concrete
counterpart: turning an empty directory into a repository, seeing what is inside the
resulting `.git` directory, and understanding which configuration layer determines
the author's identity. The next lesson will set up the course's example repository
from scratch.

### Creating and Configuring a Repository

Source: https://academia.sh/en/courses/introduction-to-version-control/creating-and-configuring-a-repository

Turning an empty directory into a repository, the contents of the `.git` directory, and the precedence of the system, user, and repository configuration layers.

The previous two lessons built the model: history is a directed acyclic graph, and in
a distributed system the whole of this graph stands locally. This lesson makes the
model concrete — the course's example repository, the `sozluk` project, is created,
and the repository's counterpart on disk is examined.

It is necessary to start with configuration, because a commit's author is read from
configuration, and the author information enters the commit's identity.

#### Three Configuration Layers

Settings are kept in three separate files, and the narrower scope overrides the wider
one:

| Layer | Scope | Option |
|---|---|---|
| System | All users on the machine | `--system` |
| User | All of a user's repositories | `--global` |
| Repository | Only the repository currently in | `--local` |

If the same key is defined in more than one layer, the repository-level value takes
effect. This makes possible the distinction "I generally work under this identity,
but in this repository I use a different one."

Three settings are made at the user level:

```bash
git config --global user.name "Deniz Kaya"
git config --global user.email "deniz@ornek.test"
git config --global init.defaultBranch main
```

The first two fill in commits' author field; if either is undefined, an attempt to
write a commit stops with an error. The third sets the name of newly created
repositories' first branch. The **branch** concept is the subject of the Branching
and Collaboration course; here it will appear only as a name visible in output.

Where settings are written can be read:

```bash
git config --list --global
```

```
user.name=Deniz Kaya
user.email=deniz@ornek.test
init.defaultbranch=main
```

Notice that a key name's section and name parts are case-insensitive:
`init.defaultBranch` was written, `init.defaultbranch` was listed. Values, however,
are preserved letter for letter.

#### Fixing the Date

A commit's identity is computed not only from its content but also from its author and date;
if the same file is committed at a different moment, a different hash comes out. So that your
output matches the lessons in this course, two environment variables that fix the author and
committer dates are provided:

```bash
export GIT_AUTHOR_DATE="2024-03-04 09:12:00 +0300"
export GIT_COMMITTER_DATE="2024-03-04 09:12:00 +0300"
```

The variables are valid only in the terminal session that is open. Because commits in later
lessons are spread across different days, the date is written again before each commit; if
you skip fixing it, everything works, only the hashes and dates differ from those shown in the
lessons.

#### Creating the Repository

A repository is set up inside an existing directory:

```bash
mkdir sozluk
cd sozluk
git init
```

The command writes a single-line confirmation: the phrase `Initialized empty Git
repository in` is followed by the full path of the created `.git` directory. Because
the path depends on the directory the command was run in, it differs on every
system.

From this moment on, the directory is a repository. The directory's visible content
has not changed; the only thing added is a hidden subdirectory named `.git`. The
repository is that subdirectory itself: if `.git` is deleted, the files remain,
history is gone.

#### The Contents of the `.git` Directory

```bash
ls .git
```

```
branches
config
description
HEAD
hooks
info
objects
refs
```

Four of the eight entries will be in constant use throughout this course:

- **`objects`** — the object database. The content-addressed storage covered in the
  previous lesson takes place here: file contents, directory images, and commits sit
  in this directory as files named by their hash values. It is empty in a new
  repository.
- **`refs`** — refs that bind human-readable names to object identities.
  `refs/heads` holds branch tips, `refs/tags` holds tags. Both are empty at the
  start.
- **`HEAD`** — a single-line file pointing to the ref currently being worked on.
- **`config`** — the repository's own configuration; the narrowest of the three
  layers.

The remaining four are used more rarely: `hooks` holds scripts to be run on certain
events, `info` holds repository-specific helper files, `description` holds a description
text that only some server interfaces read; `branches` is a remnant of an old layout and
is not used in this course. This list was taken on Linux with GNU `ls` and a UTF-8
locale: the order depends on the locale (with `LC_COLLATE=C`, `HEAD` comes first) and
the number of entries can vary with the git version. Your output may differ slightly
from this list; the four core entries are present everywhere.

The `HEAD` file can be read directly:

```bash
cat .git/HEAD
```

```
ref: refs/heads/main
```

The content is not an object identity but a reference to a reference. Because no
commit has been written yet, the `refs/heads/main` file does not exist either;
`HEAD` points to a target that does not exist. This temporary inconsistency will be
resolved once the first commit is written.

#### The Repository's Own Configuration

```bash
cat .git/config
```

```
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
```

These values are read from the system and written when the repository is created.
`repositoryformatversion` states the repository format's version, `bare` whether the
repository has a working directory, `logallrefupdates` whether ref changes are
logged. The output above was taken from a Linux system; on file systems that do not
distinguish case in file names (the usual configurations of macOS and Windows), an
`ignorecase = true` line is also written here, and on macOS `precomposeunicode = true`
is added as well.

To write a setting at the repository level, the command is run from inside the
repository, with the `--local` option:

```bash
git config --local user.email "deniz@baska-kurum.ornek"
```

This line affects only commits in the `sozluk` repository; the user-level value
stays in effect in other repositories. That is how the layers override one another;
since all commits in this course will be written under a single identity, remove the
repository-level setting right away:

```bash
git config --local --unset user.email
```

#### How the Repository's Root Is Found

Commands are not required to be run from the repository's root directory. When a
command runs, `.git` is searched for upward, starting from the directory currently
in; the first `.git` found determines the repository's root.

To try it, two empty directories are created inside the repository and the innermost one is entered:

```bash
mkdir -p temp/sub
cd temp/sub
git rev-parse --show-toplevel
```

{/* mask: (?<=/tmp/ders-)[a-z0-9_]+ */}
```
/tmp/ders-p9bhqa2r/sozluk
```

The output is the repository's root directory's full path; on your own machine the start of
the path will differ (the `/tmp/ders-…` part here is the temporary directory in which this
lesson was measured). Run from a subdirectory, it returns the same
value; where the root is does not change based on where you are. The relative path
between the root and where you are can also be asked for separately:

```bash
git rev-parse --show-prefix
```

```
temp/sub/
```

After the experiment, you return to the root and the empty directories are removed:

```bash
cd ../..
rmdir temp/sub temp
```

Because the search goes upward, running a second `git init` inside a repository does
not give the expected result: a new `.git` is created, and that subtree breaks off
from the outer repository. Separate mechanisms exist for cases that require nested
repositories; this course will use a single repository.

A command run outside a repository gives a clear error:

```
fatal: not a git repository (or any of the parent directories): .git
```

#### Finding Where a Value Comes From

Which layer an unexpected setting comes from can be asked directly:

```bash
git config --list --show-origin --global
```

{/* mask: (?<=/tmp/ders-)[a-z0-9_]+ */}
```
file:/tmp/ders-p9bhqa2r/.ev/.gitconfig	user.name=Deniz Kaya
file:/tmp/ders-p9bhqa2r/.ev/.gitconfig	user.email=deniz@ornek.test
file:/tmp/ders-p9bhqa2r/.ev/.gitconfig	init.defaultbranch=main
```

At the start of every line is the file the value was read from; the path above is the
temporary home directory in which the lesson was measured, and in your output the
`.gitconfig` file in your own home directory appears. If the layer option is removed,
all three layers are listed merged together, and it is possible for the same key to
appear on more than one line; in that case, the **last** line is the one that takes effect.

A single key's effective value is asked for like this:

```bash
git config user.name
```

```
Deniz Kaya
```

#### Initial Status

```bash
git status
```

```
On branch main

No commits yet

nothing to commit (create/copy files and use "git add" to track)
```

Three lines give three separate pieces of information. The first is the name of the
ref that the `HEAD` file points to. The second says that the `refs/heads/main` ref
has not yet been bound to an object. The third reports that nothing in the working
directory is ready to be recorded.

`git status` is the most frequently run command in this course. It answers the same
question every time: what differences exist between the working directory, the
staging area, and the last commit? The next topic will define these three regions
one by one.

#### Summary

- Configuration is kept in system, user, and repository layers; a narrow scope
  overrides a wide one.
- If `user.name` and `user.email` are not defined, a commit cannot be written; these
  values enter the commit's identity.
- `git init` creates the `.git` subdirectory in an existing directory; the
  repository is that subdirectory.
- `.git/objects` holds the object database, `.git/refs` holds named refs,
  `.git/HEAD` holds the ref currently being worked on.
- `--show-origin` shows which file a setting comes from.

#### Next Step

The repository is set up, but empty. What happens when a file is created — does it
go straight into history, or is there an intermediate stage? The next lesson will
define the staging area that stands between the working directory and the object
database, and show that area's counterpart inside `.git`.

## The Basic Cycle

The three-region model, the atomic-commit habit, writing commit messages, reading history, examining diffs, and ignore rules.

### Working Directory and Staging Area

Source: https://academia.sh/en/courses/introduction-to-version-control/working-directory-and-staging-area

The definition of the three-region model; the two separate writes `git add` makes to the object database and the index; verifying the blob object and content-addressed storage by eye.

The repository is set up and empty. The question of what happens when a file is
created was left open: does the file go straight into history? The answer is no. There
is a third region between the working directory and history, and this region is the
fundamental design decision that sets working with `git` apart from many other version
control tools.

This lesson defines that region, shows its counterpart on disk, and verifies by
computation how a file's content is named in the object database.

#### Three Regions

```
  working directory        staging area              object database
  (files)                  (.git/index)               (.git/objects)
        │                       │                          │
        │──── git add ─────────>│                          │
        │                       │──── git commit ─────────>│
```

The **working directory** is the editable state of the files — what the text editor
sees. The **staging area** is the intermediate region where the content headed into the
next commit is gathered. The **object database** is where the permanent record is kept.

The intermediate region's existence is not a convenience, it is an authority: a
commit's content stops being at the mercy of the working directory's current state. If
five files have changed in the working directory, two of them can be staged and
recorded as a separate commit. The atomic-commit habit covered in the next lesson rests
on this authority.

#### The First Files

Two files are added to the example repository. The files are created from the terminal: the
`cat > file <<'END'` line writes the text up to the line that says `END` into the file; the
quoted `'END'` keeps the shell from touching the text. The first is `README.md`, which
introduces the project:

```bash
cat > README.md <<'END'
# Sözlük

Bilgisayar bilimi terimlerinin Türkçe karşılıklarını tutan küçük bir liste.

## Biçim

`terimler.txt` dosyasında her satır şu biçimdedir:

    turkce | english
END
```

The second is the term list itself, `terimler.txt`:

```bash
cat > terimler.txt <<'END'
yığıt | stack
kuyruk | queue
çizge | graph
END
```

The repository does not know about these files yet:

```bash
git status
```

```
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	README.md
	terimler.txt

nothing added to commit but untracked files present (use "git add" to track)
```

An **untracked** file is a file with no record in the staging area. The repository
sees it but does not track it; it does not report even if its content changes.

#### `git add` Does Two Jobs

```bash
git add README.md
git status --short
```

```
A  README.md
?? terimler.txt
```

In the short format, every line begins with two columns. The **first column shows the
difference between the staging area and the last commit**, the **second column shows
the difference between the working directory and the staging area**. `A` means
"added", `M` "modified", `D` "deleted", `??` "not tracked". Since `A` is in the first
column and the second column is empty: the file has been staged and matches its state
in the working directory.

The command made two separate writes. The first is to the object database:

```bash
find .git/objects -type f
```

```
.git/objects/49/daf9067744c72d7f5427d163f983c326ff37b5
```

The file's content was stored permanently, even though no commit has been written yet.
This object is a **blob**: an object type that holds only a byte sequence, with no
file name or permission information. Its name is computed from its content; the
directory name is the hash's first two characters, the file name is the rest.

The second write is to the staging area:

```bash
git ls-files -s
```

```
100644 49daf9067744c72d7f5427d163f983c326ff37b5 0	README.md
```

This line is one entry in the staging area: file mode, blob ID, stage number, and
path. File mode `100644` denotes an ordinary file, `100755` an executable one. The
stage number is used in merge conflicts and is zero in the ordinary case.

The second file is added too:

```bash
git add terimler.txt
git ls-files -s
```

```
100644 49daf9067744c72d7f5427d163f983c326ff37b5 0	README.md
100644 9b9ef7052d54292add1ed75823a454c1fe66e3bb 0	terimler.txt
```

#### Forms of Staging

Besides writing files one by one, there are also batch forms, and their scopes differ:

| Form | Scope |
|---|---|
| `git add <path>` | The given path |
| `git add .` | The current directory and below |
| `git add -A` | The entire repository, independent of the current directory |
| `git add -p` | Selecting changes hunk by hunk |

The difference between the first two forms becomes clear in subdirectories. Suppose a
file has been deleted at the root, and a file has been created in a subdirectory named
`new`. To try this without damaging the `sozluk` repository, a disposable repository is
opened next to it; so that the file to be deleted can be a tracked file, a commit is also
written into it:

```bash
git init -q ../scratch
cd ../scratch
cp ../sozluk/terimler.txt .
git add terimler.txt
git commit -q -m "First commit of the scratch repository"
rm terimler.txt
mkdir new
echo "merhaba" > new/a.txt
```

Now enter the subdirectory and run the command from there. A command run from the
subdirectory does not cover the deletion:

```bash
cd new
git add .
git status --short
```

```
A  a.txt
 D ../terimler.txt
```

The delete mark stayed in the second column: it was not staged. The `-A` form, run
from the root, picks up both (in the short display paths are written relative to the current
directory, so you first go up to the root):

```bash
cd ..
git add -A
git status --short
```

```
A  new/a.txt
D  terimler.txt
```

The experiment is over; the disposable repository is deleted and you return to the `sozluk`
directory:

```bash
cd ../sozluk
rm -rf ../scratch
```

The fourth form lets part of the changes in a single file be staged: the command
presents each hunk of the diff in turn and asks whether to stage it. The selection
authority the staging area grants here drops from the file level to the line level; if
two independent fixes were made in one file, they can be split into separate commits.

#### The Staging Area Is a File

The staging area is not an abstract concept; it is a binary file called `.git/index` —
the **index**. It holds one entry for every tracked path: the path name, the mode, the
blob ID, and a few cache fields read from the file system.

These last fields determine how fast `git status` runs. Instead of rehashing the
content of every file in the working directory, the file's size and modification time
are compared against the record in the staging area; if both match, the file is
treated as unchanged. As the record count grows, this cache's value grows with it.

Notice that what the staging area holds is a **sorted file list**: it says which path
is bound to which content. This structure is the direct precursor of the tree object
covered in the next lesson.

#### Where the ID Comes From

A blob's ID is not the raw hash of the file's content. A header stating the object's
type and length is prepended to the object, and the hash is computed over that whole.
The header's format is: the object type, a space, the length in bytes, and a zero
byte.

This computation can be done from outside. Take an eight-byte piece of content:

```bash
printf 'merhaba\n' | git hash-object --stdin
```

```
e995d8e1c89654fd0a6453c2da61fb6e9da262e6
```

The same value can also be obtained by adding the header by hand and using a
general-purpose hashing tool:

```bash
printf 'blob 8\0merhaba\n' | shasum
```

```
e995d8e1c89654fd0a6453c2da61fb6e9da262e6  -
```

The two values match. The `shasum` command produces the SHA-1 digest; on the GNU
toolset, the same job is done with `sha1sum`.

This computation has two consequences. First, even if the same content is stored
under two different names, it produces a single object; the ID depends on the
content, not the file name. Second, if a single byte of the object changes, its ID
changes beyond recognition — the avalanche effect defined in the Data Structures
course.

The object's type and size can be queried directly:

```bash
git cat-file -t 49daf9067744c72d7f5427d163f983c326ff37b5
git cat-file -s 49daf9067744c72d7f5427d163f983c326ff37b5
```

```
blob
187
```

This ID belongs to `README.md`'s content at that moment; unless you write the file
letter for letter the same in your own repository, you will see a different value.
Every object ID shown throughout the course was produced by this same rule.

#### Summary

- The working directory holds the files being edited, the staging area holds the
  content headed into the next commit, the object database holds the permanent
  records.
- `git add` does two jobs: it writes the content into the object database as a blob,
  and it places an entry in the staging area binding the path to the blob ID.
- The staging area is the `.git/index` file; it holds entries made up of path, mode,
  and blob ID.
- A blob's ID is the hash of the content with a type-and-length header prepended; if
  the content is the same, the ID is the same.
- In the short status display, the first column reports the staging area, the second
  the working directory.

#### Next Step

The staging area now has two entries, but history is still empty; running `git log`
would get no response. The next lesson turns the staging area's content into a
permanent record, opens up and examines the tree and commit objects created in the
process, and defines when a commit counts as "a single piece of work."

### Committing Changes

Source: https://academia.sh/en/courses/introduction-to-version-control/committing-changes

The staging area turning into a permanent record; the content of tree and commit objects, the four object types, the role of refs, and the atomic-commit habit.

The staging area has two entries, but history is still empty. This lesson turns those
two entries into a permanent record and opens up, one by one, the objects created in
the process. At the end, it discusses why a commit should hold "a single piece of
work."

#### The First Commit

The `GIT_AUTHOR_DATE` and `GIT_COMMITTER_DATE` variables from the previous lesson are still
defined in the open session; if you opened a new terminal, write them again with the same
values.

```bash
git commit -m "Terim listesini ve biçim belgesini ekle"
```

```
[main (root-commit) 13d31bf] Terim listesini ve biçim belgesini ekle
 2 files changed, 12 insertions(+)
 create mode 100644 README.md
 create mode 100644 terimler.txt
```

The `root-commit` in square brackets reports that this commit has no parent — that it
is the source of the graph. `13d31bf` is the shortened form of the commit ID. Because
the ID is computed from the author, the time, the message, and the content, you will
see a different value in your own repository; this holds for every ID shown throughout
the course.

```bash
git status
```

```
On branch main
nothing to commit, working tree clean
```

All three regions now show the same content: the working directory, the staging
area, and the last commit line up.

#### Inside the Commit Object

A commit is a piece of text stored in the object database, and it can be read
directly:

```bash
git cat-file -p HEAD
```

```
tree 378e434005c6c42021d4398a15e89149b0fd8671
author Deniz Kaya <deniz@ornek.test> 1709532720 +0300
committer Deniz Kaya <deniz@ornek.test> 1709532720 +0300

Terim listesini ve biçim belgesini ekle
```

There are four components:

- **`tree`** — the ID of the tree object holding the project's exact snapshot at that
  moment.
- **`author`** and **`committer`** — the person who wrote and the person who recorded
  the change, followed by a Unix timestamp and a time zone offset. Why the two fields
  are separate will be seen in the third topic.
- **The text after the blank line** — the commit message.

Every commit other than the root commit also has a `parent` line. Since the parent
commit's ID is part of the text, it also enters the ID computation: changing a past
commit changes the ID of every commit after it.

#### The Tree Object

```bash
git ls-tree HEAD
```

```
100644 blob 49daf9067744c72d7f5427d163f983c326ff37b5	README.md
100644 blob 9b9ef7052d54292add1ed75823a454c1fe66e3bb	terimler.txt
```

The **tree object** holds a directory's content: every line has a mode, an entry
type, an object ID, and a name. If the entry type is `blob`, it is a file; if `tree`,
a subdirectory. Subdirectories link to their own tree objects; the project directory
thereby turns into a tree structure whose root is the tree the commit points to. In
the terms of the Data Structures course: internal nodes are tree objects, leaves are
blobs.

Notice that the blob IDs are the same as their values in the staging area. `git
commit` did not write new blobs; it turned the staging area's entries into a tree
object and created a commit object pointing at that tree.

```bash
find .git/objects -type f | sort
```

```
.git/objects/13/d31bf17fdc8a5292b7fff288de94088d5c1440
.git/objects/37/8e434005c6c42021d4398a15e89149b0fd8671
.git/objects/49/daf9067744c72d7f5427d163f983c326ff37b5
.git/objects/9b/9ef7052d54292add1ed75823a454c1fe66e3bb
```

Four objects: two blobs, one tree, one commit.

#### The Four Object Types

| Type | What it holds | What it links to |
|---|---|---|
| blob | File content | — |
| tree | A directory's entries | Blobs and subtrees |
| commit | A snapshot and its metadata | A tree and parent commits |
| tag | A named pointer and its note | Usually a commit |

All four are stored the same way: a type-and-length header, followed by the content,
with the ID being the hash of that whole. The fourth type is covered in the course's
last lesson.

#### Refs

A commit ID being a forty-character hash does not mean a person has to hold that
value in their head. Refs fill this gap:

```bash
cat .git/refs/heads/main
```

```
13d31bf17fdc8a5292b7fff288de94088d5c1440
```

The `refs/heads/main` file holds a single line: a commit ID. The previous lesson
showed that the `HEAD` file points to this ref. The chain is: `HEAD` →
`refs/heads/main` → commit → tree → blobs.

```bash
git rev-parse HEAD
```

```
13d31bf17fdc8a5292b7fff288de94088d5c1440
```

`git rev-parse` is the command that turns a name into an object ID. Names such as
`HEAD`, `HEAD~1` (the previous commit), and `main` are all resolved this way.

#### Atomic Commits

A search script is added to the example project. `ara.sh` is created and given execute
permission:

```bash
cat > ara.sh <<'END'
#!/bin/sh
# terimler.txt içinde arama yapar.

if [ $# -ne 1 ]; then
    echo "kullanım: ara.sh ARANAN" >&2
    exit 1
fi

grep -- "$1" terimler.txt
END
chmod +x ara.sh
```

While writing the script, a typo is noticed in the format example inside
`README.md`, and the line `turkce | english` is corrected to `türkçe | english`:

```bash
sed 's/turkce | english/türkçe | english/' README.md > README.new
mv README.new README.md
```

The working directory now has two unrelated changes:

```bash
git status --short
```

```
 M README.md
?? ara.sh
```

Recording these two together in a single commit leaves a blurry entry in history,
something like "new script and a typo fix." An **atomic commit** is the smallest
consistent set of changes that serves a single purpose. Its criterion is this: when
the commit is undone, what remains should be consistent, and undoing it should not
take away more than intended.

The staging area is what makes this separation possible. Let this commit have been made an hour
and a half after the first one; the date variables are moved to the new time (from now on this will
be done before every commit):

```bash
export GIT_AUTHOR_DATE="2024-03-04 10:40:00 +0300" GIT_COMMITTER_DATE="2024-03-04 10:40:00 +0300"
git add ara.sh
git commit -m "Terim arama betiği ekle"
```

```
[main 546c174] Terim arama betiği ekle
 1 file changed, 9 insertions(+)
 create mode 100755 ara.sh
```

The `create mode 100755` line shows that the file was recorded with execute
permission; the permission information is held in the tree object and is part of
history.

The second change is recorded separately. Changes in tracked files can be committed
directly, without staging, using the `-a` option:

```bash
export GIT_AUTHOR_DATE="2024-03-04 10:52:00 +0300" GIT_COMMITTER_DATE="2024-03-04 10:52:00 +0300"
git commit -a -m "Biçim örneğindeki yazımı düzelt"
```

```
[main 3132c78] Biçim örneğindeki yazımı düzelt
 1 file changed, 1 insertion(+), 1 deletion(-)
```

The `-a` option covers only tracked files; an untracked file does not enter a commit
this way. Its convenience comes at the cost of disabling the selection authority the
staging area provides — for this reason, it should not be used when more than one
concern is touched.

#### Unchanged Content Is Not Rewritten

```bash
git ls-tree HEAD
```

```
100644 blob c0a6e3d5d4d0a0d117519512a1fb771c8a60bfee	README.md
100755 blob 4bc98c87b25ceac31de2e48015e81a943d2e73c8	ara.sh
100644 blob 9b9ef7052d54292add1ed75823a454c1fe66e3bb	terimler.txt
```

The `terimler.txt` blob is identical to its value in the first commit: `9b9ef70`.
Because the file has not changed across three commits, a single object is shared by
three trees. This is the answer to the snapshot model's objection of "does every
record rewrite all the files": since the same content produces the same ID, there is
nothing left to write a second time.

#### Summary

- `git commit` turns the staging area's entries into a tree object and writes a
  commit object pointing at that tree.
- A commit object contains a tree ID, author and committer information, a message,
  and (except for the root commit) parent IDs.
- A tree object holds a directory's entries; subdirectories link to their own tree
  objects.
- There are four object types: blob, tree, commit, tag. All are named by the hash of
  content with a type-and-length header.
- An atomic commit is the smallest set of changes that leaves a consistent state
  when undone; the staging area makes this split possible.

#### Next Step

Three commits have been written, and all three have a one-line message. The reason
for a fix, the options tried and abandoned, or the grounds for a decision do not fit
on one line. The next lesson takes up the commit message's structure — subject line,
body, and rationale — and the rules that make this structure keep history readable.

### Writing Commit Messages

Source: https://academia.sh/en/courses/introduction-to-version-control/writing-commit-messages

The message structure made up of the subject line, the blank separator, and the body; recording the rationale, and how tools make use of the message format.

Three commits have been written, and all three had a one-line message. This is enough
for small changes; but the reason for a fix, the options tried and abandoned, or the
grounds for a design decision do not fit on one line. This lesson defines the
message's structure.

The **commit message** is not a formality; it is the one free-text field that
determines whether history can be read. *What* a line does is read from the code;
*why* it was written that way is read only from the message.

#### The Message's Structure

The message has three parts:

```
Subject line: a summary of the change

Body: the rationale for the change. What the problem was, why this
solution was chosen, and which options were ruled out are written here.

A second paragraph is added if needed.
```

What separates the parts is **the first blank line**, and this separator is a formal
rule: tools read the first line as the subject and everything after the blank line
as the body. If no blank line is inserted, the entire text counts as the subject and
gets squeezed onto a single line in summary listings.

#### The Subject Line

Four rules are settled for the subject line:

1. **Written in the imperative mood.** "Sort the term list," not "Sorted the term
   list" or "I sorted the term list." The reason is formal: tool-generated messages
   are also imperative (`Revert "…"`, `Merge …`), so history reads in one
   grammatical mood. Test: the subject line should complete "If applied, this
   commit will: …."
2. **Kept to around fifty characters.** There is no hard limit, but the line gets
   truncated in summary listings and narrow interfaces. A subject line over fifty
   characters is often a sign the commit holds more than one piece of work.
3. **Not ended with a period.** It is a title, not a sentence.
4. **States what changed, not how.** The detail is the body's job.

#### The Body

The question the body answers is not "what was done" but **"why it was done."** The
change itself is already visible in the diff; what is not visible in the diff is the
situation that required that change.

A useful body includes:

- **The problem.** Which behavior was wrong, or which need went unmet?
- **The rationale.** Why was this path chosen?
- **Options ruled out.** An approach tried and abandoned, so the same path is not
  retried.
- **Known limits.** Situations the solution does not cover.

Lines are wrapped by hand at around seventy-two characters. The reason is that `git
log`'s output writes the message body with a four-space indent: to avoid overflow in
an eighty-column space, the body itself has to be narrower.

#### Example

Three terms are added to the term list and the message is written. When `git commit` is run
with no option, the configured text editor opens; once the file is saved and closed, its
content becomes the message. If it is left empty, no commit is written. Here `-F -` is used
instead of the editor: the message is read from standard input, so the command below can be
run on its own. The previous three commits were written on 4 March 2024; because this commit
falls on the next day, the date is updated first:

```bash
export GIT_AUTHOR_DATE="2024-03-05 09:05:00 +0300"
export GIT_COMMITTER_DATE="2024-03-05 09:05:00 +0300"
cat >> terimler.txt <<'END'
ağaç | tree
karma tablosu | hash table
bağlı liste | linked list
END
git add terimler.txt
git commit -q -F - <<'MSG'
Üç veri yapısı terimi ekle

Ağaç, karma tablosu ve bağlı liste terimleri listede yoktu; bu terimler
sorulduğunda arama betiği boş çıktı veriyordu.

Karşılıklar Veri Yapıları kursunun sözlüğüyle aynı biçimde yazıldı.
MSG
```

The `-q` option suppresses the commit's summary output; the result will be read with `git log`
below. The subject line says what was done; the body records *why* the terms were considered
missing and where the equivalents came from. Both are pieces of information that cannot be
extracted from the code.

A second example belongs to a fix in the search script. `-i` is added to the script's `grep`
call and the commit is written the same day in the afternoon:

```bash
export GIT_AUTHOR_DATE="2024-03-05 14:20:00 +0300"
export GIT_COMMITTER_DATE="2024-03-05 14:20:00 +0300"
cat > ara.sh <<'END'
#!/bin/sh
# terimler.txt içinde arama yapar.

if [ $# -ne 1 ]; then
    echo "kullanım: ara.sh ARANAN" >&2
    exit 1
fi

grep -i -- "$1" terimler.txt
END
git commit -q -a -F - <<'MSG'
Aramayı büyük/küçük harften bağımsız yap

Terimler listede küçük harfle tutuluyor. Kullanıcı "Yığıt" yazdığında
arama boş dönüyor, terim listede olduğu hâlde bulunamıyordu.

grep çağrısına -i seçeneği eklendi; bu seçenek POSIX'te tanımlıdır ve
ek bağımlılık getirmez.
MSG
```

This message records more than the one-line diff itself: why the `-i` option was
added, and why another solution — uppercasing the list, say — was not chosen instead.

How tools use the subject/body distinction can be seen directly in the messages of the
two commits. `%s` gives the subject line, `%b` the body:

```bash
git log -1 --format='%s' 43370ca
```

```
Üç veri yapısı terimi ekle
```

```bash
git log -1 --format='%b' 43370ca
```

```
Ağaç, karma tablosu ve bağlı liste terimleri listede yoktu; bu terimler
sorulduğunda arama betiği boş çıktı veriyordu.

Karşılıklar Veri Yapıları kursunun sözlüğüyle aynı biçimde yazıldı.
```

#### What Not to Write

Some message patterns add nothing to history:

- **Subject lines that repeat the content.** "terimler.txt updated" — already
  visible in the diff. Write the work done, not the file name.
- **Status patterns.** "wip," "temporary," "fix." These do not say what the commit
  does and leave no searchable trace.
- **A bare external number, with no context.** A message with only a ticket number
  is empty for a reader without access to that system. A number goes in the body,
  with a one-sentence summary of the problem.
- **Subject lines that count more than one job.** The "and" in "Added X and fixed
  Y" is a sign the commit should be split.

The last item shows how the commit message ties back to the atomic-commit habit:
when a good subject line cannot be written, the problem is most often not in the
message but in the commit's scope.

#### Ways to Give the Message

| Way | Use |
|---|---|
| `git commit` | Opens the editor; the usual way for a multi-line message |
| `git commit -m "subject"` | A one-line message |
| `git commit -m "subject" -m "body"` | Each `-m` becomes a paragraph, with a blank line inserted between them |
| `git commit -F file` | The message is read from a file |

The template that opens in the editor has lines starting with `#`; these are
excluded from the message and summarize the current state. Reading this summary
before writing is the cheapest way to notice the wrong files were staged.

#### Result

```bash
git log --oneline
```

```
60e13bd Aramayı büyük/küçük harften bağımsız yap
43370ca Üç veri yapısı terimi ekle
3132c78 Biçim örneğindeki yazımı düzelt
546c174 Terim arama betiği ekle
13d31bf Terim listesini ve biçim belgesini ekle
```

Five lines describe the project's five steps in readable form. This readability
comes directly from two habits: every commit holding a single piece of work, and
every subject line summarizing that work in the imperative. Break either one, and
the list loses its value.

#### A Message Is a Search Field

Messages are not just read, they are also searched. The subject and body text can be
filtered:

```bash
git log --oneline --grep="arama"
```

```
60e13bd Aramayı büyük/küçük harften bağımsız yap
43370ca Üç veri yapısı terimi ekle
546c174 Terim arama betiği ekle
```

The search is done letter for letter; word stems are not recognized. This detail
stands out in this repository's Turkish commit messages:

```bash
git log --oneline --grep="betik"
```

The command returns no lines, even though history holds the commit "Terim arama
betiği ekle" (Add term search script). The reason is Turkish consonant softening:
when a suffix attaches to "betik" (script), the final `k` softens to `ğ`, so
"betiği" never contains the substring "betik" at all. A shortened pattern does
return a result:

```bash
git log --oneline --grep="beti"
```

```
43370ca Üç veri yapısı terimi ekle
546c174 Terim arama betiği ekle
```

Because `--grep` matches substrings literally rather than recognizing word stems,
this kind of miss can arise in any language whose suffixes alter the stem; Turkish
consonant softening is the case this repository's history happens to show. It makes
choosing subject-line words that resist this kind of alteration a practical
necessity.

#### Summary

- The message is made up of a subject line and a body; the first blank line
  separates the two, and tools rely on this separation.
- The subject line is written in the imperative mood, at around fifty characters,
  with no period.
- The body answers "why," not "what": the statement of the problem, the rationale
  for the solution, options ruled out, and known limits.
- Body lines are wrapped at around seventy-two characters; `git log`'s output
  writes the body indented.
- Messages can be searched with `--grep`; this turns writing subject lines
  consistently into a practical necessity.

#### Next Step

History has reached five commits and was listed with `git log --oneline`. But
listing is only the simplest form of reading history: commits touching a specific
file, commits adding or removing specific text, and a specific time range can each
be queried separately. The next lesson takes up the tools for reading history.

### Reading History

Source: https://academia.sh/en/courses/introduction-to-version-control/reading-history

Formatting the log's output; filtering by path, content, author, and time; revision-range notation, and reading the commit graph directly.

History has reached five commits. `git log --oneline` is enough to list these five
lines; but as history grows, the question changes. Instead of "what were the last
five commits," the questions become "who touched this file," "when was this line
added," "what happened between two versions." This lesson takes up the filtering and
formatting tools that answer those questions.

#### The Default Format

```bash
git log -1
```

```
commit 60e13bdaf4dd2f895e9b562c4a8ee9ea289d2af7
Author: Deniz Kaya <deniz@ornek.test>
Date:   Tue Mar 5 14:20:00 2024 +0300

    Aramayı büyük/küçük harften bağımsız yap

    Terimler listede küçük harfle tutuluyor. Kullanıcı "Yığıt" yazdığında
    arama boş dönüyor, terim listede olduğu hâlde bulunamıyordu.

    grep çağrısına -i seçeneği eklendi; bu seçenek POSIX'te tanımlıdır ve
    ek bağımlılık getirmez.
```

The default format gives the ID at full length, the author and date on separate
lines, and the whole message indented. The `-1` option limits the output to a
single commit; any number can be given with `-n`.

The ID, date, and author fields shown in the output are specific to this
repository. When you run the same commands in your own repository, the IDs and
dates will differ; what stays fixed is the output's structure.

#### Summary Formats

```bash
git log --oneline
```

```
60e13bd Aramayı büyük/küçük harften bağımsız yap
43370ca Üç veri yapısı terimi ekle
3132c78 Biçim örneğindeki yazımı düzelt
546c174 Terim arama betiği ekle
13d31bf Terim listesini ve biçim belgesini ekle
```

The shortened ID is kept just long enough to preserve uniqueness; its length can
grow as the repository grows. The short ID can be used in commands in place of the
full ID.

The `--graph` option draws history's graph structure along the left edge:

```bash
git log --oneline --graph
```

```
* 60e13bd Aramayı büyük/küçük harften bağımsız yap
* 43370ca Üç veri yapısı terimi ekle
* 3132c78 Biçim örneğindeki yazımı düzelt
* 546c174 Terim arama betiği ekle
* 13d31bf Terim listesini ve biçim belgesini ekle
```

Since there is no branching in this history, the drawing is a single column. If
there is branching, the columns multiply and merge points become visible; this
output will be read in the Branching and Collaboration course.

#### Format Strings

The output's fields can be selected one by one:

```bash
git log --pretty=format:'%h %ad %s' --date=short
```

```
60e13bd 2024-03-05 Aramayı büyük/küçük harften bağımsız yap
43370ca 2024-03-05 Üç veri yapısı terimi ekle
3132c78 2024-03-04 Biçim örneğindeki yazımı düzelt
546c174 2024-03-04 Terim arama betiği ekle
13d31bf 2024-03-04 Terim listesini ve biçim belgesini ekle
```

Commonly used fields:

| String | Meaning |
|---|---|
| `%H` / `%h` | Commit ID / its shortened form |
| `%T` | The tree object's ID |
| `%P` | Parent commits' IDs |
| `%an` / `%ae` | Author's name / email |
| `%ad` / `%cd` | Author date / committer date |
| `%s` / `%b` | Subject line / body |

The `--date` option sets the date format: `short` gives only the day, `iso` gives
the full timestamp and time zone.

#### Change Statistics

```bash
git log --oneline --stat
```

```
60e13bd Aramayı büyük/küçük harften bağımsız yap
 ara.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
43370ca Üç veri yapısı terimi ekle
 terimler.txt | 3 +++
 1 file changed, 3 insertions(+)
3132c78 Biçim örneğindeki yazımı düzelt
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
546c174 Terim arama betiği ekle
 ara.sh | 9 +++++++++
 1 file changed, 9 insertions(+)
13d31bf Terim listesini ve biçim belgesini ekle
 README.md    | 9 +++++++++
 terimler.txt | 3 +++
 2 files changed, 12 insertions(+)
```

Under every commit, the files it touched and the number of lines added and removed
are listed. If a commit's `--stat` output is long, that commit has most likely
combined more than one piece of work: the statistic is the audit tool for the
atomic-commit habit.

#### Path Filtering

Commits that touch a specific file:

```bash
git log --oneline -- ara.sh
```

```
60e13bd Aramayı büyük/küçük harften bağımsız yap
546c174 Terim arama betiği ekle
```

The double-dash separator declares that everything after it is a path. Without the
separator, `ara.sh` gets interpreted as an attempt at a ref name; when a branch and
a file share the same name, the command becomes ambiguous. Always writing the
separator removes this ambiguity.

#### Content Filtering

To find when a piece of text entered or left history:

```bash
git log --oneline -S 'grep -i'
```

```
60e13bd Aramayı büyük/küçük harften bağımsız yap
```

The `-S` option finds commits that **change the number of occurrences** of the
given string. That is, it returns not the commits that contain the string, but the
commits that add or remove it. It is the most direct tool when searching for a
behavior's origin: what is being searched for is not the line itself, but the
moment the line was born.

#### Author and Time Filtering

```bash
git log --oneline --since="2024-03-05 00:00"
```

```
60e13bd Aramayı büyük/küçük harften bağımsız yap
43370ca Üç veri yapısı terimi ekle
```

`--since` selects everything after the given moment, `--until` everything before
it. When a time of day is added to the date, the boundary becomes exact; short
forms that give only the day can be interpreted differently.

There is a separate command for the distribution of contributions:

```bash
git shortlog -sn HEAD
```

```
     5	Deniz Kaya
```

`-s` asks for just the count, `-n` for descending order by count. When no ref is
given, the command reads its input from standard input; this is why `HEAD` has to
be written explicitly in scripts.

#### Revision Range Notation

Two dots define a set of commits. `A..B` means "commits reachable from B but not
reachable from A":

```bash
git log --oneline HEAD~2..HEAD
```

```
60e13bd Aramayı büyük/küçük harften bağımsız yap
43370ca Üç veri yapısı terimi ekle
```

The notation `HEAD~2` means "two parents back." The definition is given over the
graph: reachability is the set of nodes reachable by following parent edges. This
is why range notation is also meaningful in branched histories; asking for
everything after the point where two branches diverged is done with the same
notation.

#### Viewing a Single Commit

```bash
git show --stat 43370ca
```

```
commit 43370ca23c4b30992ea9857c6282416de8e0a816
Author: Deniz Kaya <deniz@ornek.test>
Date:   Tue Mar 5 09:05:00 2024 +0300

    Üç veri yapısı terimi ekle

    Ağaç, karma tablosu ve bağlı liste terimleri listede yoktu; bu terimler
    sorulduğunda arama betiği boş çıktı veriyordu.

    Karşılıklar Veri Yapıları kursunun sözlüğüyle aynı biçimde yazıldı.

 terimler.txt | 3 +++
 1 file changed, 3 insertions(+)
```

`git show` displays an object in a form suited to its type: metadata and diff for
a commit, an entry list for a tree, raw content for a blob.

#### Reading the Graph Directly

Format strings are also useful for giving history's graph structure in its bare
form:

```bash
git log --format='%h %T %P'
```

```
60e13bd d23f16ee3533d05e3afd189a11551b6ec89f0898 43370ca23c4b30992ea9857c6282416de8e0a816
43370ca 8074917cfe2b0200648feaeeaf612170157dcc95 3132c7841e3e63c5eb968e37b301aa64dd1e8fb0
3132c78 01426328c4d751777bad684a17c7bc7941589603 546c174a007fdea08f907bd4d428cc87ce7e6148
546c174 21f7408f587a9837da6ae2ba960c74b756c79858 13d31bf17fdc8a5292b7fff288de94088d5c1440
13d31bf 378e434005c6c42021d4398a15e89149b0fd8671
```

Every line gives a node; the third column gives that node's edge. In the last
line, the third column is empty: the root commit has no parent. The values in the
first column reappear in the third column starting from the second line; the list
is the graph's edge-list representation. One of the three graph representations
defined in the Data Structures course shows up here directly.

#### Summary

- The default log format gives the ID, author, date, and the whole message;
  `--oneline` reduces these to a single line.
- `--pretty=format:` selects fields one by one; `%H`, `%T`, `%P` give the commit,
  tree, and parent IDs.
- `--stat` shows the files touched and the line counts per commit.
- Path filtering is done with the double-dash separator, content filtering with
  `-S`; `-S` finds commits that change a string's occurrence count.
- The `A..B` notation defines commits reachable from B but not reachable from A.

#### Next Step

The log says which commit touched which file, but it does not show the change's
content. What is more, changes not yet committed never appear in the log at all.
The next lesson takes up examining the differences between the three
regions — working directory and staging area, staging area and last commit,
between two commits — one at a time, and reading the diff output's format.

### Examining Diffs

Source: https://academia.sh/en/courses/introduction-to-version-control/examining-diffs

Three separate comparisons between the working directory, the staging area, and commits; reading the unified diff format, and diffing at the object level.

The log says which commit touched which file, but it does not show the change's
content. What is more, changes not yet committed never appear in the log at all: an
edit in the working directory is outside history until it is committed.

This lesson defines three separate forms of taking a **diff** and shows how to read
diff output. The three-region model earns its practical value here: the question of
which two regions are being compared determines which option to write.

#### Three Comparisons

| Command | Compared | Question it answers |
|---|---|---|
| `git diff` | Working directory ↔ staging area | What have I not staged yet? |
| `git diff --staged` | Staging area ↔ last commit | What will go into the next commit? |
| `git diff HEAD` | Working directory ↔ last commit | What has changed in total since the last commit? |

The third row is the union of the first two. `--staged`'s synonym is `--cached`; the
former will be used throughout the course.

#### Working Directory and Staging Area

The search script stays silent when no match is found and exits with status zero.
The script is changed to this. The file is rewritten under the same name; `cat >` replaces the
content of an existing file and the execute permission is kept:

```bash
cat > ara.sh <<'END'
#!/bin/sh
# terimler.txt içinde arama yapar.

if [ $# -ne 1 ]; then
    echo "kullanım: ara.sh ARANAN" >&2
    exit 1
fi

if ! grep -i -- "$1" terimler.txt; then
    echo "eşleşme yok: $1" >&2
    exit 2
fi
END
```

```bash
git diff
```

```
diff --git a/ara.sh b/ara.sh
index b87baa8..582ddde 100755
--- a/ara.sh
+++ b/ara.sh
@@ -6,4 +6,7 @@ if [ $# -ne 1 ]; then
     exit 1
 fi
 
-grep -i -- "$1" terimler.txt
+if ! grep -i -- "$1" terimler.txt; then
+    echo "eşleşme yok: $1" >&2
+    exit 2
+fi
```

#### The Anatomy of Diff Output

The output is in **unified diff** format and has four parts.

**The header line.** `diff --git a/ara.sh b/ara.sh` — the two versions being
compared are named with the `a/` and `b/` prefixes. `a` is the old side, `b` the
new.

**The object line.** `index b87baa8..582ddde 100755` — the old and new blob IDs,
together with the file mode. This line records which two objects in the object
database the diff was computed between. If the mode is the same on both sides, a
single value is written at the end of the line; if the mode has changed, two
separate lines are added.

**The file lines.** `---` marks the old version, `+++` the new. For a newly
created file, the old side is `/dev/null`.

**The hunk header.** Every region where a change occurs is called a **hunk**, and
every hunk opens with a header. `@@ -6,4 +6,7 @@` — it carries four numbers: 4
lines starting at line 6 of the old file have been replaced by 7 lines starting at
line 6 of the new file. The text `if [ $# -ne 1 ]; then` at the end of the header
is the nearest enclosing line that gives the change's context; it is not part of
the diff itself.

Lines inside a hunk begin with one of three marks: a space (an unchanged context
line), `-` (removed), `+` (added). Context lines default to three; this is changed
with the `-U` option. A modified line appears in diff format as a delete-add
pair — there is no "modify" operation in the format.

#### Staging Area and Last Commit

```bash
git add ara.sh
git diff
```

The command prints nothing: the working directory and the staging area now match.
The diff has moved:

```bash
git diff --staged
```

```
diff --git a/ara.sh b/ara.sh
index b87baa8..582ddde 100755
--- a/ara.sh
+++ b/ara.sh
@@ -6,4 +6,7 @@ if [ $# -ne 1 ]; then
     exit 1
 fi
 
-grep -i -- "$1" terimler.txt
+if ! grep -i -- "$1" terimler.txt; then
+    echo "eşleşme yok: $1" >&2
+    exit 2
+fi
```

`git add` did not move the content; it changed which blob the staging area points
at. Because the diff itself is computed between the same two objects, the output
is letter-for-letter identical.

#### Two Regions at Once

A line is added to the term list but not staged:

```bash
echo "küme | set" >> terimler.txt
git status --short
```

```
M  ara.sh
 M terimler.txt
```

The meaning of the two columns becomes clear here: `ara.sh` is marked in the
first column (staged), `terimler.txt` in the second (only in the working
directory).

```bash
git diff
```

```
diff --git a/terimler.txt b/terimler.txt
index 11660ec..17af3b8 100644
--- a/terimler.txt
+++ b/terimler.txt
@@ -4,3 +4,4 @@ kuyruk | queue
 ağaç | tree
 karma tablosu | hash table
 bağlı liste | linked list
+küme | set
```

`git diff` shows only the `terimler.txt` change, `git diff --staged` only the
`ara.sh` change. To see both at once, the comparison is made against `HEAD`:

```bash
git diff HEAD --stat
```

```
 ara.sh       | 5 ++++-
 terimler.txt | 1 +
 2 files changed, 5 insertions(+), 1 deletion(-)
```

`--stat` gives a per-file summary instead of line-by-line output. The bar made of
plus and minus marks shows the ratio of added to removed lines at scale; for
large diffs, the total count is fitted to a fixed width.

#### The Diff Between Two Commits

When two refs are given, the diff describes the transition from the first to the
second:

```bash
git diff 13d31bf 60e13bd -- terimler.txt
```

```
diff --git a/terimler.txt b/terimler.txt
index 9b9ef70..11660ec 100644
--- a/terimler.txt
+++ b/terimler.txt
@@ -1,3 +1,6 @@
 yığıt | stack
 kuyruk | queue
 çizge | graph
+ağaç | tree
+karma tablosu | hash table
+bağlı liste | linked list
```

Order matters: `git diff B A` shows the same diff in the reverse direction. The
path separator is used here too, to limit the output to a single file.

#### Diffing at the Object Level

Comparison is not limited to refs; the `ref:path` notation names a specific
object at that ref:

```bash
git diff 13d31bf:README.md 3132c78:README.md
```

```
diff --git a/README.md b/README.md
index 49daf90..c0a6e3d 100644
--- a/README.md
+++ b/README.md
@@ -6,4 +6,4 @@ Bilgisayar bilimi terimlerinin Türkçe karşılıklarını tutan küçük bir l
 
 `terimler.txt` dosyasında her satır şu biçimdedir:
 
-    turkce | english
+    türkçe | english
```

This notation makes the comparison possible even if the file name changed
between the two versions.

#### Word-Level Diff

Line-based diff shows a small change in a long line as the whole line being
deleted and re-added. `--word-diff` improves readability in this case:

```bash
git diff --word-diff HEAD -- terimler.txt
```

```
diff --git a/terimler.txt b/terimler.txt
index 11660ec..17af3b8 100644
--- a/terimler.txt
+++ b/terimler.txt
@@ -4,3 +4,4 @@ kuyruk | queue
ağaç | tree
karma tablosu | hash table
bağlı liste | linked list
{+küme | set+}
```

The added portion is enclosed in `{+ +}`, the removed portion in `[- -]`. The
presentation format has changed; the diff itself and the content to be recorded
are the same.

#### Recording the Changes

The two changes are split into separate commits. Because the commits fall on the next day, 6 March
2024, the date is rewritten before each commit:

```bash
export GIT_AUTHOR_DATE="2024-03-06 11:00:00 +0300"
export GIT_COMMITTER_DATE="2024-03-06 11:00:00 +0300"
git commit -m "Eşleşme bulunamadığında hata bildir"
export GIT_AUTHOR_DATE="2024-03-06 11:18:00 +0300"
export GIT_COMMITTER_DATE="2024-03-06 11:18:00 +0300"
git commit -a -m "Küme terimini ekle"
```

```
[main 2c883a7] Eşleşme bulunamadığında hata bildir
 1 file changed, 4 insertions(+), 1 deletion(-)
[main ece664b] Küme terimini ekle
 1 file changed, 1 insertion(+)
```

The first command recorded the staged `ara.sh` change; the second picked up the
remaining tracked change with the `-a` option.

#### Summary

- `git diff` compares the working directory with the staging area, `--staged`
  compares the staging area with the last commit, and the form called with `HEAD`
  compares both at once.
- Unified diff output is made up of header, object, file, and hunk lines; the
  hunk header gives the starting line and line count on the old and new sides.
- There is no "modify" operation in the format; a changed line appears as one
  deletion and one addition.
- Giving two refs diffs between commits; the `ref:path` notation diffs between
  objects.
- `--stat` gives a per-file summary, `--word-diff` gives a word-level display.

#### Next Step

The repository so far holds only files written by hand. But files also get
produced while working: the script's output, backups left by the editor, build
results. Letting these into history is both noise and risk. The next lesson takes
up the ignore rules that determine which files are kept out of the repository,
and why those rules do not apply to tracked files.

### Ignore Rules

Source: https://academia.sh/en/courses/introduction-to-version-control/ignore-rules

Files that should not enter the repository; `.gitignore` pattern syntax; finding a rule's source, and why ignore rules do not apply to tracked files.

So far, the repository holds only files written by hand. But files also get
produced while working: the script's output, backups left by the editor, build
results. These get listed at every `git status` call, lowering the visibility of
the real changes, and can end up in a commit by mistake.

This lesson takes up which files are kept out of the repository, and how this
decision gets recorded.

#### What Should Not Enter the Repository

Four classes are distinguished:

- **Derived files.** Everything that can be produced from the sources already in
  the repository: build outputs, data produced by scripts, temporary reports.
  Keeping them in history is unnecessary, because they can be regenerated from
  the source; what is more, they show up as a constant diff since they change on
  every regeneration.
- **Local environment files.** Editor settings, metadata files the operating
  system leaves in directories, personal notes. These belong to the person, not
  the project.
- **Secrets.** Passwords, access keys, private certificates. Keeping these out
  of the repository is not a matter of tidiness, it is a security requirement.
- **Large binary files.** Every version is stored as a separate object and
  cannot be compressed, since no diff can be taken. They take up space in every
  copy of the repository.

The third item calls for a separate warning. As noted in the previous topic, in a
distributed repository, history spreads to every copy. Even if a password is
deleted after being committed, the deletion only adds a new commit; the old
commit containing the password stays in place and is present in every copy of
the repository. In this case, the only correct action is to invalidate the
secret.

#### The `.gitignore` File

In the example repository, running the script produces a log file and an output
directory; the editor also leaves a backup. To produce them by hand, the script is run twice and a
backup of `README.md` is taken:

```bash
mkdir cikti
./ara.sh stack > cikti/son-arama.txt
./ara.sh queue > ara.log
cp README.md "README.md~"
```

The status now looks like this:

```bash
git status --short
```

```
?? README.md~
?? ara.log
?? cikti/
```

The rules are written into a text file named `.gitignore`:

```bash
cat > .gitignore <<'END'
# Betiğin ürettiği çıktılar
ara.log
cikti/

# Düzenleyici yedekleri
*~
END
```

```bash
git status --short
```

```
?? .gitignore
```

Three entries dropped off the list; what is left is the file carrying the rules
itself. `.gitignore` is committed to the repository: the rules belong to the
project and should be the same in every copy of the repository.

```bash
export GIT_AUTHOR_DATE="2024-03-06 16:35:00 +0300"
export GIT_COMMITTER_DATE="2024-03-06 16:35:00 +0300"
git add .gitignore
git commit -m "Üretilen dosyaları yoksayma listesine al"
```

```
[main 9b23cc3] Üretilen dosyaları yoksayma listesine al
 1 file changed, 6 insertions(+)
 create mode 100644 .gitignore
```

#### Pattern Syntax

Every line is a **pattern**: a string matched against path names that can
contain wildcard characters.

| Pattern | Meaning |
|---|---|
| `*.log` | Files with the `.log` extension, at any depth |
| `cikti/` | Only directories named `cikti`, and their contents |
| `/gecici.tmp` | Only `gecici.tmp` at the repository root |
| `not?.txt` | Single-character wildcard: `not1.txt`, `not2.txt` |
| `a/**/veri.bin` | `veri.bin` at any depth under `a` |
| `!ara.log` | Excludes a file covered by a previous rule from the rule |
| `# text` | A comment line |

Two details are often confused. The first is **the position of the slash**: if
there is a slash inside or at the start of the pattern, the pattern is resolved
relative to the directory the `.gitignore` file is in; otherwise it matches at
any depth. `/gecici.tmp` catches only the file at the root, not
`alt/gecici.tmp`. The second is **the trailing slash**: `cikti/` matches only
directories, not a file named `cikti`.

Rules are evaluated in order, and **the last matching rule** takes effect. This
is why a rule starting with `!` has to be written after the pattern it covers;
if written first, it has no effect.

#### Why a File Is Ignored

In long rule lists, which line is in effect can be asked directly:

```bash
git check-ignore -v ara.log cikti/son-arama.txt "README.md~"
```

```
.gitignore:2:ara.log	ara.log
.gitignore:3:cikti/	cikti/son-arama.txt
.gitignore:6:*~	README.md~
```

Every line gives the file the rule is in, the line number, the pattern, and the
matching path. If a rule starting with `!` matches, it is listed too; in that
case, the file is not ignored, because the last matching rule excluded it from
the rule.

All ignored files can also be listed:

```bash
git status --short --ignored
```

```
!! README.md~
!! ara.log
!! cikti/
```

The `!!` mark denotes ignored entries.

#### Ignoring Does Not Apply to Tracked Files

A file holding local notes gets committed by mistake. The file is created first and the date
is moved to the next day:

```bash
cat > notlar-yerel.md <<'END'
# Yerel notlar
Yarın: karma tablosu örneğini gözden geçir.
Kitap: bölüm 4'ü bitir.
END
export GIT_AUTHOR_DATE="2024-03-07 09:10:00 +0300"
export GIT_COMMITTER_DATE="2024-03-07 09:10:00 +0300"
git add notlar-yerel.md
git commit -m "Yerel notları ekle"
```

```
[main 7e44d29] Yerel notları ekle
 1 file changed, 3 insertions(+)
 create mode 100644 notlar-yerel.md
```

The mistake is noticed, and the file name is added to `.gitignore`:

```bash
echo "notlar-yerel.md" >> .gitignore
```

But nothing changes:

```bash
git ls-files
```

```
.gitignore
README.md
ara.sh
notlar-yerel.md
terimler.txt
```

The file is still tracked. The rule not applying is not a flaw, it is a
consequence of the definition: ignore rules determine whether **untracked**
files get listed or not. If a file has a record in the staging area, it is
already tracked, and the rules have nothing to do with it. Otherwise, adding a
rule could make tracked files silently drop out of history.

For the same reason, `git check-ignore` stays silent too: by default, the
command does not evaluate tracked paths.

The fix is to remove the file from the staging area:

```bash
git rm --cached notlar-yerel.md
```

```
rm 'notlar-yerel.md'
```

The `--cached` option deletes only the record in the staging area; the file in
the working directory stays in place. Without the option, the file is deleted
from disk too.

```bash
export GIT_AUTHOR_DATE="2024-03-07 09:25:00 +0300"
export GIT_COMMITTER_DATE="2024-03-07 09:25:00 +0300"
git commit -a -m "Yerel notları izlemeden çıkar"
```

```
[main d3ab7b0] Yerel notları izlemeden çıkar
 2 files changed, 1 insertion(+), 3 deletions(-)
 delete mode 100644 notlar-yerel.md
```

After this commit, the file is no longer tracked, and the `.gitignore` rule
takes effect. The file's old content, however, still sits in history: commit
`7e44d29` is still reachable. Ignoring does not clean up the past; it only puts
the future in order.

#### Three Ignore Layers

| Location | Scope | Shared? |
|---|---|---|
| `.gitignore` | The directory it is in, and below | Yes, committed to the repository |
| `.git/info/exclude` | This repository only | No, stays local |
| The file pointed to by `core.excludesFile` | All of the user's repositories | No |

The distinction is made by this criterion: does the rule belong to the
**project**, or to a **work habit**? The build output's name belongs to the
project and is written to `.gitignore`. The temporary files left by the editor
in use belong to the person; rather than adding these to every project's
`.gitignore`, the right place to write them is the user-level file.

Rules specific to this repository but not meant to be shared are written to the
`.git/info/exclude` file, and it uses the same syntax:

```bash
echo 'trial-*.txt' >> .git/info/exclude
git check-ignore -v trial-1.txt
```

```
.git/info/exclude:7:trial-*.txt	trial-1.txt
```

Since the output's first field shows which file the rule came from, confusion
between the three layers is resolved with the same command.

#### Summary

- Derived files, local environment files, secrets, and large binary files
  should not enter the repository.
- Rules are written to the `.gitignore` file; the file is committed to the
  repository and applies in every copy.
- In patterns, the slash's position determines the scope; a trailing slash
  limits the pattern to directories; the last matching rule takes effect.
- Ignore rules affect only untracked files; a tracked file has to be removed
  from tracking with `git rm --cached`.
- Rules can be kept in three layers: the file committed to the repository, the
  repository-specific local file, and the user-level file.

#### Next Step

The basic cycle is complete: a change is made, staged, committed, history is
read. But this cycle has been told in one direction only. What happens when the
wrong file gets staged, an edit needs to be taken back, or a commit is written
wrong? The next topic takes up the tools for undoing, and its first lesson
starts with the narrowest-scoped ones — unstaging and restoring a file.

## Undoing

Unstaging and restoring files, amending a commit, reset modes, reverting, the stash, and tagging.

### Unstaging and Restoring Files

Source: https://academia.sh/en/courses/introduction-to-version-control/unstaging-and-restoring-files

Undoing the staging-area record and the working-directory content separately; choosing a source, and which situations are recoverable.

The Basic Cycle was told in one direction: a change is made, staged, committed. This
topic takes up the reverse direction. The first lesson defines the two narrowest-scoped
operations; neither touches history, both affect only the working directory and the
staging area.

The distinction has to be established from the start, because the two operations undo
different things, and one costs more than the other.

#### Two Separate Kinds of Undoing

| Operation | Region affected | Source | What is lost |
|---|---|---|---|
| Unstaging | Staging area | Last commit | Nothing |
| Restoring a file | Working directory | Staging area or a commit | Unrecorded edits |

The first operation is reversible: unstaged content keeps standing in the working
directory. The second is not — if the edit in the working directory was not stored
anywhere, it is gone once overwritten.

#### The Mistake

While two terms are being added to the term list, an edit is also made to the search
script: the `-x` option is added to the `grep` call. This option requires the match to
cover the whole line, and it breaks the script. The edits are made from the command line:

```bash
printf 'öbek | heap\ndizi | array\n' >> terimler.txt
sed -i.bak 's/grep -i --/grep -ix --/' ara.sh
rm ara.sh.bak
```

The script is now broken:

```bash
./ara.sh yığıt
```

```
eşleşme yok: yığıt
```

The term is in the list, yet it cannot be found: `-x` requires the pattern "yığıt" to
equal the entire `yığıt | stack` line. Without the mistake being noticed, two changes are
staged together:

```bash
git add .
git status --short
```

```
M  ara.sh
M  terimler.txt
```

The term addition should be kept; the edit in the script should be undone.

#### Unstaging

```bash
git restore --staged ara.sh
git status --short
```

```
 M ara.sh
M  terimler.txt
```

`ara.sh`'s mark moved from the first column to the second: the file is no longer in the
staging area, it is changed only in the working directory. The command returned the
staging-area entry to its state at the last commit; it did not touch the working
directory.

To confirm this, the diff is taken:

```bash
git diff
```

```
diff --git a/ara.sh b/ara.sh
index 582ddde..fd7e235 100755
--- a/ara.sh
+++ b/ara.sh
@@ -6,7 +6,7 @@ if [ $# -ne 1 ]; then
     exit 1
 fi
 
-if ! grep -i -- "$1" terimler.txt; then
+if ! grep -ix -- "$1" terimler.txt; then
     echo "eşleşme yok: $1" >&2
     exit 2
 fi
```

The edit is still standing. At this point there are two options: fix the edit by hand, or
undo it entirely.

#### Restoring the Working Directory

```bash
git restore ara.sh
git status --short
```

```
M  terimler.txt
```

```bash
./ara.sh yığıt
```

```
yığıt | stack
```

The script works again. `git restore` returned the file to its state in the staging area;
and since the staging area is identical to the last commit, the result is the file's
state at the last commit.

This command is **destructive**. The edit it undoes was never stored anywhere; it had no
record in the object database or in the staging area. After the command runs, that text
cannot be brought back. As a rule, it should not be run without being certain the edit to
be undone is really unwanted; if there is any doubt, the change should first be staged or
put in the stash. The stash is the subject of this topic's fifth lesson.

The remaining change is committed, and the date is moved forward for the new commit:

```bash
export GIT_AUTHOR_DATE="2024-03-07 14:00:00 +0300"
export GIT_COMMITTER_DATE="2024-03-07 14:00:00 +0300"
git commit -m "Öbek ve dizi terimlerini ekle"
```

```
[main 0e74ba9] Öbek ve dizi terimlerini ekle
 1 file changed, 2 insertions(+)
```

#### Choosing the Source

By default, `git restore`'s source is the staging area. The `--source` option makes
another commit the source:

```bash
git restore --source=HEAD~1 terimler.txt
git diff --stat
```

```
 terimler.txt | 2 --
 1 file changed, 2 deletions(-)
```

The file returned to its state at the previous commit; two lines were removed. This is a
way to inspect a past version or bring back a single file. The change is only in the
working directory; history stays as it was, and `git restore terimler.txt` returns to the
latest state:

```bash
git restore terimler.txt
```

The regions the command affects can be chosen explicitly:

| Form | Affected |
|---|---|
| `git restore <path>` | Working directory |
| `git restore --staged <path>` | Staging area |
| `git restore --staged --worktree <path>` | Both |

The third row returns a file entirely to its state at the last commit.

#### Bringing Back a Deleted File

Deletion is a change too, and it is undone with the same tool. If the term list is
deleted by mistake:

```bash
rm terimler.txt
git status --short
```

```
 D terimler.txt
```

The `D` mark is in the second column: the file has been removed from the working
directory, but its record in the staging area is still standing. Since the record
carries a blob ID, the content is in the object database:

```bash
git restore terimler.txt
git status --short
```

The second command prints nothing; the file has come back. If the deletion was staged
too (if `D` appears in the first column), it is necessary to unstage first, then
restore; `--staged --worktree` does both in a single command.

This is a direct consequence of the previous section's criterion: the deleted file's
content was tracked, so it was in the object database. When an untracked file is deleted,
it cannot be brought back.

#### Older Forms

The same jobs can be done with older commands, and these forms are common in
documentation. To try them, a line is added to the file and staged:

```bash
echo "deneme | trial" >> terimler.txt
git add terimler.txt
```

Then the two older forms are applied in turn:

```bash
git reset HEAD terimler.txt      # unstaging
git checkout -- terimler.txt     # restoring the working directory
```

The first command prints this notice:

```
Unstaged changes after reset:
M	terimler.txt
```

The problem with the two older forms is that the commands are overloaded: `git reset` is
also the command that rewinds history, `git checkout` is also the command that switches
between branches. The same name producing very different results makes it hard to read
which operation is being done from the command. `git restore` and `git switch` were
defined to name this distinction. The older forms remain valid; this course will use the
new names.

#### What Can Be Recovered

The limit of the undo tools reduces to a single criterion: **was the content written to
the object database?**

| State | In the object database | Recoverable |
|---|---|---|
| Committed content | Yes | Yes |
| Staged content | Yes (as a blob) | Yes |
| Working-directory-only edit | No | No |
| Untracked file | No | No |

The second row can be unexpected: the moment `git add` runs, the content is written as a
blob and stays in the database even if no commit follows. Even content that has been
unstaged and then overwritten can still be read with `git cat-file`, as long as its ID is
known. An edit that was never staged, by contrast, leaves no trace at all.

The practical consequence is this: a change worked on over a long stretch is protected
against a destructive command if it is staged from time to time. This is the staging
area's second function, beyond preparing for a commit.

#### Summary

- Unstaging affects the staging area, restoring a file affects the working directory.
- `git restore --staged` returns the record to its state at the last commit and loses no
  data.
- `git restore` undoes the edit in the working directory destructively.
- With `--source`, a commit can be chosen as the source; `--staged --worktree` restores
  both regions at once.
- The criterion for recoverability is whether the content was written to the object
  database; staging provides this guarantee.

#### Next Step

These tools undo changes that have not yet been committed. What happens if a commit that
has already been written is missing something or has a mistake? The next lesson takes up
amending the last commit, why this operation produces a new object, and in which
situations it should not be used.

### Amending a Commit

Source: https://academia.sh/en/courses/introduction-to-version-control/amending-a-commit

Fixing the last commit's message or content; producing a new object because a commit is immutable, the reflog, and the limit of shared history.

The previous lesson undid changes that had not yet been committed. This lesson goes one
step further: it takes up what to do if a commit that has already been written is
missing something or has a mistake. The two most common cases are a mistyped message and
a file that was forgotten from the commit.

#### Amending Does Not Actually Change Anything

A commit object is immutable. Its ID is the hash of its content; if the content changes,
the ID changes too, and a different object results. This is why the operation called
"amending a commit" does not edit the existing object: it creates a **new** commit
attached to the same parent, and moves the branch tip to that new commit. The old commit
stays in the object database but becomes unreachable from any ref.

The distinction matters in practice: fixing the past is rewriting the past.

#### Fixing the Message

A usage section is added to `README.md` and committed; but the message has two typos. Because the author date is preserved after this commit
too, the date is fixed first:

```bash
cat >> README.md <<'END'

## Kullanım

    ./ara.sh ARANAN
END
export GIT_AUTHOR_DATE="2024-03-08 11:30:00 +0300"
export GIT_COMMITTER_DATE="2024-03-08 11:30:00 +0300"
git add README.md
git commit -m "READMEye kullanm bölümü ekle"
```

```
[main b55cf62] READMEye kullanm bölümü ekle
 1 file changed, 4 insertions(+)
```

```bash
export GIT_COMMITTER_DATE="2024-03-08 11:33:00 +0300"
git commit --amend -m "README'ye kullanım bölümü ekle"
```

```
[main b2ed6c2] README'ye kullanım bölümü ekle
 Date: Fri Mar 8 11:30:00 2024 +0300
 1 file changed, 4 insertions(+)
```

The ID went from `b55cf62` to `b2ed6c2`. Not a single byte of the content changed; the
only thing that changed was the message, and the message enters the ID computation too.

The `Date:` line in the output is printed to report that the commit's author date was
preserved.

That the only thing that changed is really the message can be tested by comparing tree
IDs:

```bash
git log -1 --format='%h %T' HEAD@{1}
git log -1 --format='%h %T' HEAD
```

```
b55cf62 4e359f7361da5c8654dce5682f26e5a79e43f5ca
b2ed6c2 4e359f7361da5c8654dce5682f26e5a79e43f5ca
```

The two commits point at the same tree. The project content is byte-identical, the
commit IDs are different: this is direct proof that the ID covers metadata as well as
content. The only thing newly written to the object database is the second commit
object; the blob and tree objects are shared.

#### Author and Committer

The reason the commit object's two date fields are kept separate shows up here:

```bash
git log -1 --format='author   : %an %ad%ncommitter: %cn %cd' --date=iso
```

```
author   : Deniz Kaya 2024-03-08 11:30:00 +0300
committer: Deniz Kaya 2024-03-08 11:33:00 +0300
```

The **author** is the person who wrote the change, and the time that change was first
created. The **committer** is the person who put the commit into the object database,
and the time of that recording. Amending preserves the author field and updates the
committer field. Keeping the two fields separate ensures the source is not lost in cases
where a contribution is placed into history by someone else.

#### Adding a Forgotten Change

`README.md` had been committed without the line describing the exit code. The missing
line is added, staged, and folded into the commit while keeping the message:

```bash
printf '\nEşleşme bulunamazsa betik 2 koduyla çıkar.\n' >> README.md
export GIT_COMMITTER_DATE="2024-03-08 11:41:00 +0300"
git add README.md
git commit --amend --no-edit
```

```
[main 377b1f8] README'ye kullanım bölümü ekle
 Date: Fri Mar 8 11:30:00 2024 +0300
 1 file changed, 6 insertions(+)
```

`--no-edit` uses the existing message without opening the editor. The line count went
from four to six: the staging area's content formed the new commit's tree.

History shows a single commit:

```bash
git log --oneline -n 3
```

```
377b1f8 README'ye kullanım bölümü ekle
0e74ba9 Öbek ve dizi terimlerini ekle
d3ab7b0 Yerel notları izlemeden çıkar
```

Three separate commit objects were written — `b55cf62`, `b2ed6c2`, `377b1f8` — but only
the last one stands in history. The first two became unreachable.

#### The Scope of Amending

`--amend` builds the new commit's tree from **the staging area's current state**. This is
as much a trap as a convenience: if the staging area holds files staged for other work,
they enter the amended commit too, and end up recorded in the wrong place in history.

When only the message needs fixing, the staging area needs to match the last commit. The
check is a single command:

```bash
git status --short
```

The output's first column should carry no mark. If it does, those entries will enter the
scope of the amend.

The same mechanism applies to the author field. The `--author` option sets the new
commit's author; a commit written while the configured identity was wrong is fixed this
way. The committer field is still taken from the configuration, so the two fields
diverge — this is the expected result of keeping the fields separate.

#### What Happened to the Old Commits

Being unreachable is not the same as being gone:

```bash
git cat-file -t HEAD@{2}
```

```
commit
```

The object is in place. The way to reach it is the **reflog**: a local log that records
what values `HEAD` and branch tips took on in the past.

```bash
git reflog -n 3
```

```
377b1f8 HEAD@{0}: commit (amend): README'ye kullanım bölümü ekle
b2ed6c2 HEAD@{1}: commit (amend): README'ye kullanım bölümü ekle
b55cf62 HEAD@{2}: commit: READMEye kullanm bölümü ekle
```

The `HEAD@{n}` notation names the value of the `HEAD` ref `n` steps ago. The log is
local and is not shared; an amend made in one copy of the repository does not show up in
another copy's log.

Unreachable objects do not stay forever. **Garbage collection**, which runs during
repository maintenance, deletes objects unreachable from any ref and from the reflog. The
default retention period is long enough; but it is not correct to work under the
assumption that "an unreachable object is permanent." The recovery window is limited.

#### Bringing Back a Lost Commit

The reflog is not just a record; it is a recovery tool. The way to make a commit that was
amended by mistake reachable again is to give it a name:

```bash
git branch recovery HEAD@{2}
git log --oneline -n 2 recovery
```

```
b55cf62 READMEye kullanm bölümü ekle
0e74ba9 Öbek ve dizi terimlerini ekle
```

The command runs silently and creates a ref named `recovery`. Because the object is now
reachable from a name, it falls outside the scope of garbage collection; once the
content has been inspected and whatever was needed has been taken, this name can be
deleted.

The same thing can be done with an ID: `b55cf62` from the reflog can be written directly.
The `HEAD@{n}` notation is preferred, because which step to go back to is read from the
sequence, not the ID.

The general rule here is this: a single name that can reach an object is enough to
protect it. Operations like amending and the resetting seen in the next lesson do not
delete objects; they only move the names pointing to them.

#### The Limit of Amending

Amending applies only to the **last** commit, and is safe only on **unshared** commits.

The reason for the second condition is that the ID covers the chain of parents. When a
commit is amended, its ID changes; every commit after it is then forced to change its
parent field too, and their IDs change. History, from the amended point on, ends up made
entirely of new objects.

A copy that already has this history keeps holding on to its own old chain. The result
is two separate chains, in two copies, that contain the same changes but carry different
IDs; when someone tries to merge them, every commit appears twice. In a repository worked
on collaboratively, this is a situation to avoid.

The rule is simple: if a commit has been shared, it is not amended. Instead, a new
commit is written that reverses its effect — reverting, covered in a later lesson.

Amending is also a narrow tool because it is limited to the last commit alone. When an
older commit needs fixing, history has to be rewritten from that point on; these
operations will be covered in the Advanced Git course.

#### Summary

- A commit object is immutable; amending creates a new commit attached to the same
  parent and moves the branch tip to it.
- Because the ID covers the message too, fixing only the message still produces a new
  ID.
- The author field is preserved, the committer field is updated; keeping the two fields
  separate preserves the contribution's source.
- Old commits become unreachable but are not deleted; the reflog makes it possible to
  reach them. Garbage collection deletes unreachable objects after a certain period.
- Amending applies only to the last commit, and only to unshared history.

#### Next Step

Amending targets the last commit and moves the branch tip to a new object. It is also
possible to move the branch tip further back — that is, to drop more than one commit from
history — and this operation has three different modes. The next lesson shows, one by
one, what these modes do to the working directory and the staging area.

### Reset Modes

Source: https://academia.sh/en/courses/introduction-to-version-control/reset-modes

Moving the branch tip backward; the effect of the `--soft`, `--mixed`, and `--hard` modes on the three regions, lost content, and recovery through the reflog.

Amending moved the branch tip to a new commit attached to the same parent. **Reset**
moves the branch tip further back in history: one or more commits drop out of the
branch's reachable past. Reset has three modes, and the difference among them is which
regions are affected.

This lesson applies all three modes, one by one, to the same mistake.

#### The Mistake

The term list is meant to be sorted alphabetically and is handed to a general-purpose
sorting tool. The language setting is turned off so the tool works on byte values
(`LC_ALL=C`); this way the result comes out the same on any machine:

```bash
LC_ALL=C sort terimler.txt -o terimler.txt
cat terimler.txt
```

```
ağaç | tree
bağlı liste | linked list
dizi | array
karma tablosu | hash table
kuyruk | queue
küme | set
yığıt | stack
çizge | graph
öbek | heap
```

The order is wrong. "çizge" and "öbek" have dropped to the end of the list, and "küme"
comes after "kuyruk." The reason is that the sort is done by byte value, not by the
Turkish alphabet: as seen in the How Computers Work course, letters outside the ASCII
range are encoded in UTF-8 with more than one byte, and their first byte is greater than
every ASCII letter. The correct order requires a locale-aware comparison scheme.

Without the mistake being noticed, it is committed. Since the same commit will be rewritten
several times in this lesson, the date is fixed first; these two variables stay defined in the open
session, so retype them if you opened a new terminal:

```bash
export GIT_AUTHOR_DATE="2024-03-08 15:00:00 +0300"
export GIT_COMMITTER_DATE="2024-03-08 15:00:00 +0300"
git commit -a -m "Terim listesini abecesel sırala"
```

```
[main baf5138] Terim listesini abecesel sırala
 1 file changed, 5 insertions(+), 5 deletions(-)
```

#### What Reset Does

Reset consists of three steps, and the mode determines how many of them get applied:

1. **Move the branch tip.** Done in every mode.
2. **Make the staging area match the target.** Done in every mode except `--soft`.
3. **Make the working directory match the target.** Done only with `--hard`.

The first step touches history; the second and third steps touch content.

#### `--soft`

```bash
git reset --soft HEAD~1
git log --oneline -n 1
```

```
377b1f8 README'ye kullanım bölümü ekle
```

The commit dropped out of history. What happened to the content?

```bash
git status --short
```

```
M  terimler.txt
```

The change is standing in the staging area. Only the first step was applied: the branch
tip moved back, the staging area and the working directory were left as they were. The
result is the state of "the commit was never written, but the content is ready to be
committed."

```bash
git diff --staged --stat
```

```
 terimler.txt | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
```

This mode is used to rearrange the scope of the last commit: the commit is undone, the
content is split the way it is wanted, and it is recommitted.

#### `--mixed`

The reset commit is rewritten with the same message, and this time reset with the default mode:

```bash
git commit -m "Terim listesini abecesel sırala"
```

```
[main baf5138] Terim listesini abecesel sırala
 1 file changed, 5 insertions(+), 5 deletions(-)
```

```bash
git reset HEAD~1
```

```
Unstaged changes after reset:
M	terimler.txt
```

```bash
git status --short
```

```
 M terimler.txt
```

The mark moved to the second column: the content is no longer in the staging area, only
in the working directory. The first and second steps were applied. This is the default
mode even when `--mixed` is not written.

This mode undoes both the commit and the selection made in the staging area; it is used
when the decision of which files to commit together needs to be made from scratch.

#### `--hard`

The commit is written once more; the change in the working directory is staged with `-a`:

```bash
git commit -a -m "Terim listesini abecesel sırala"
```

```
[main baf5138] Terim listesini abecesel sırala
 1 file changed, 5 insertions(+), 5 deletions(-)
```

```bash
git reset --hard HEAD~1
```

```
HEAD is now at 377b1f8 README'ye kullanım bölümü ekle
```

```bash
git status --short
```

The output is empty. The term list has also returned to its old state: the sort has
been completely undone. All three steps were applied.

This mode is **destructive** and takes two separate pieces of content down with it: the
content of the reset commits, and every uncommitted edit that was in the working
directory at the time. The first can be brought back from the reflog; the second cannot.
Looking at `git status`'s output before running `git reset --hard` is the only way to
avoid this second loss.

#### Comparing the Three Modes

| Mode | Branch tip | Staging area | Working directory | Where the change stays |
|---|---|---|---|---|
| `--soft` | Moved | Untouched | Untouched | In the staging area |
| `--mixed` | Moved | Matched | Untouched | In the working directory |
| `--hard` | Moved | Matched | Matched | Nowhere |

The table reduces to a single sentence: the harsher the mode, the deeper the undo
reaches. The choice is made based on whether the commit's content should be preserved.

#### Recovery

A reset commit stays in the object database and can be found through the reflog:

```bash
git reflog -n 6
```

```
377b1f8 HEAD@{0}: reset: moving to HEAD~1
baf5138 HEAD@{1}: commit: Terim listesini abecesel sırala
377b1f8 HEAD@{2}: reset: moving to HEAD~1
baf5138 HEAD@{3}: commit: Terim listesini abecesel sırala
377b1f8 HEAD@{4}: reset: moving to HEAD~1
baf5138 HEAD@{5}: commit: Terim listesini abecesel sırala
```

The log has recorded the order the three modes were tried in exactly as it happened. It
is not a coincidence that the commit ID comes out as `baf5138` every time: as long as
the content, message, author, and date are the same, the ID is the same too. This is a
direct consequence of content-addressed storage.

The branch tip is moved back to the desired ID:

```bash
git reset --hard baf5138
```

```
HEAD is now at baf5138 Terim listesini abecesel sırala
```

The commit is back in history. In this example the sort really is wrong, so the final
decision is to drop it:

```bash
git reset --hard HEAD~1
```

```
HEAD is now at 377b1f8 README'ye kullanım bölümü ekle
```

#### The Form Given a Path

When `git reset` is given a path, its behavior changes entirely: the branch tip does not
move, only that path's record in the staging area is returned to its state at the
target. To try it, the list is sorted again:

```bash
LC_ALL=C sort terimler.txt -o terimler.txt
git add terimler.txt
git status --short
```

```
M  terimler.txt
```

```bash
git reset terimler.txt
```

```
Unstaged changes after reset:
M	terimler.txt
```

```bash
git status --short
```

```
 M terimler.txt
```

This is the older form of what was done with `git restore --staged` in the previous
lesson. The same command name doing different things depending on whether it is given a
path explains why `git restore` was named separately.

#### `ORIG_HEAD`

Before moving the branch tip, reset writes the old value to a file named `ORIG_HEAD`:

```bash
cat .git/ORIG_HEAD
```

```
baf5138febc3dd9acb9ef94162c9d2f72e8e5d0a
```

This name can be used like a ref; the shortest way to undo a reset done by mistake is to
go back to the `ORIG_HEAD` target without reading the reflog:

```bash
git reset --hard ORIG_HEAD
```

```
HEAD is now at baf5138 Terim listesini abecesel sırala
```

The `ORIG_HEAD` file records only the last operation; it is overwritten on successive
resets. Going further back requires the reflog.

Since the sort is wrong, it is dropped again after this trial; the next lesson starts from the
state at `377b1f8`:

```bash
git reset --hard HEAD~1
```

```
HEAD is now at 377b1f8 README'ye kullanım bölümü ekle
```

#### The Difference Between Reset and Restoring a File

The two commands are confused because both are called "undoing." The distinction is
clear:

- `git restore` operates on a **path**; it does not touch history.
- `git reset` operates on a **ref**; it moves the branch tip.

If you want to return a file to an old state, use `git restore`; if you want to drop a
commit from history, use `git reset`.

Reset itself is also problematic on shared history: moving the branch tip back creates
incompatibility with copies that already have those commits. The previous lesson's rule
holds here too — shared history is not rewound.

#### Summary

- Reset moves the branch tip; the mode determines whether the staging area and the
  working directory are also matched to it.
- `--soft` leaves the content in the staging area, `--mixed` in the working directory;
  `--hard` matches both to the target.
- `--hard` deletes uncommitted edits irrecoverably.
- Reset commits stay in the object database; they can be found and brought back through
  the reflog.
- The same content, message, author, and date always produce the same commit ID.

#### Next Step

Reset and amending both work by rewriting history, and this is why they cannot be used
on shared commits. So what happens if a shared commit is wrong? The next lesson takes up
the tool that reverses a commit's effect without disturbing history — reverting.

### Reverting

Source: https://academia.sh/en/courses/introduction-to-version-control/reverting

A new commit that reverses another's effect; undoing without disturbing history, verifying with the tree ID, reverting without committing, and reverting a revert.

Reset and amending both work by rewriting history; this is why they cannot be used on
shared commits. If a shared commit is wrong, only one path remains: leave the past as it
is, and write a **new** commit that reverses the mistake's effect. This operation is
called **reverting**.

#### The Mistake

The search script's output is meant to be limited to just the Turkish column, and
`grep`'s output is passed through a filter. The condition line in the script is edited with `sed`:

```bash
sed -i.bak "s/terimler.txt; then/terimler.txt | cut -d '|' -f 1; then/" ara.sh
rm ara.sh.bak
```

After the edit the condition becomes:

{/* run: skip */}
```sh
if ! grep -i -- "$1" terimler.txt | cut -d '|' -f 1; then
    echo "eşleşme yok: $1" >&2
    exit 2
fi
```

The change is recorded with a commit whose dates are fixed. The date variables stay defined in the
open session; retype them if you opened a new terminal:

```bash
export GIT_AUTHOR_DATE="2024-03-11 09:30:00 +0300"
export GIT_COMMITTER_DATE="2024-03-11 09:30:00 +0300"
git commit -a -m "Arama çıktısını yalnızca Türkçe sütuna indir"
```

```
[main d245350] Arama çıktısını yalnızca Türkçe sütuna indir
 1 file changed, 1 insertion(+), 1 deletion(-)
```

The change produces the intended output but carries a hidden flaw: the shell takes the
pipeline's exit code from the last command. Because `cut` always finishes successfully,
the condition comes out false even when `grep` finds no match:

```bash
./ara.sh yokboyle
echo "exit code: $?"
```

```
exit code: 0
```

The script printed no no-match notice and exited with code zero; yet there is no match,
and the exit code should be 2. The flaw is not in the output itself but in the error
reporting; this is why it is prone to going unnoticed.

The commit has been shared, and it cannot be dropped from history.

#### Reverting

```bash
export GIT_AUTHOR_DATE="2024-03-11 10:12:00 +0300"
export GIT_COMMITTER_DATE="2024-03-11 10:12:00 +0300"
git revert --no-edit HEAD
```

```
[main c497738] Revert "Arama çıktısını yalnızca Türkçe sütuna indir"
 Date: Mon Mar 11 10:12:00 2024 +0300
 1 file changed, 1 insertion(+), 1 deletion(-)
```

`--no-edit` accepts the prepared message without opening the editor. The script works
correctly again:

```bash
./ara.sh yokboyle
```

```
eşleşme yok: yokboyle
```

```bash
git log --oneline -n 4
```

```
c497738 Revert "Arama çıktısını yalnızca Türkçe sütuna indir"
d245350 Arama çıktısını yalnızca Türkçe sütuna indir
377b1f8 README'ye kullanım bölümü ekle
0e74ba9 Öbek ve dizi terimlerini ekle
```

The faulty commit stands in history. Alongside it, a commit that reverses its effect has
been added.

#### The Content of a Revert Commit

```bash
git show HEAD
```

```
commit c49773829409f26d221a2bb5b46a58d8ca2c8327
Author: Deniz Kaya <deniz@ornek.test>
Date:   Mon Mar 11 10:12:00 2024 +0300

    Revert "Arama çıktısını yalnızca Türkçe sütuna indir"

    This reverts commit d2453504d3bed0b24e4bedfaac3c3b87c508b799.

diff --git a/ara.sh b/ara.sh
index 64b5b70..582ddde 100755
--- a/ara.sh
+++ b/ara.sh
@@ -6,7 +6,7 @@ if [ $# -ne 1 ]; then
     exit 1
 fi
 
-if ! grep -i -- "$1" terimler.txt | cut -d '|' -f 1; then
+if ! grep -i -- "$1" terimler.txt; then
     echo "eşleşme yok: $1" >&2
     exit 2
 fi
```

The diff is the reverse of the reverted commit's diff: the deleted line has been added,
the added line has been deleted. The automatically generated message writes the
reverted commit's full ID into the body; thanks to this line, what a revert undoes can be
read straight from history.

#### Verifying the Result

Whether the revert really returned things to the previous state can be tested with
certainty by comparing tree IDs:

```bash
git log --format='%h %T %s' -n 4
```

```
c497738 961039799e5ee6f157229351478a98abb7652c82 Revert "Arama çıktısını yalnızca Türkçe sütuna indir"
d245350 0f2d862b8a585d8dfc9b1a0acbdb30a221233ade Arama çıktısını yalnızca Türkçe sütuna indir
377b1f8 961039799e5ee6f157229351478a98abb7652c82 README'ye kullanım bölümü ekle
0e74ba9 c6ec3cea3b8943f818cd8bb423c65f0cb5c553ea Öbek ve dizi terimlerini ekle
```

The revert commit's tree, `9610397`, is the same as the tree of `377b1f8`, the commit
before the faulty one. Two different commits point at the same tree: the project content
has returned to its old state, byte for byte. This equality check, which
content-addressed storage provides, removes the need to compare diffs by eye.

#### Why Reverting Does Not Disturb History

Reverting only adds: it touches no existing object, it changes no ID. Copies that
already have this history keep the chain they hold and take on just one new commit on
top. The incompatibility that arises with reset does not occur here.

The cost is that the mistake stays visible in history. This is not a flaw; it is, most
of the time, the desired behavior: when a faulty change went in, and when and why it was
undone, ends up on record.

#### Reverting Without Committing

The result of a revert can be inspected without committing it directly:

```bash
git revert -n HEAD~3
git status --short
```

```
M  terimler.txt
```

```bash
git diff --staged
```

```
diff --git a/terimler.txt b/terimler.txt
index a5350ef..17af3b8 100644
--- a/terimler.txt
+++ b/terimler.txt
@@ -5,5 +5,3 @@ ağaç | tree
 karma tablosu | hash table
 bağlı liste | linked list
 küme | set
-öbek | heap
-dizi | array
```

The `-n` option puts the change in the staging area but does not commit it. This is
used to combine multiple commits into a single revert commit, or to inspect the result
before committing it. The operation can be
canceled with `git revert --abort`:

```bash
git revert --abort
```

The command returns the staging area and the working directory to their state before the revert
began.

In the example, the reverted commit was not history's most recent one. A revert can be
applied to any commit; what is computed is that commit's diff, reversed, applied to the
current content. If the commits in between touched the same lines, the application
fails and a conflict is reported. Resolving conflicts is the subject of the Branching
and Collaboration course.

#### Reverting a Revert

A revert is a commit too; it can be reverted. This is the way to reapply a change that
was undone:

```bash
export GIT_AUTHOR_DATE="2024-03-11 15:00:00 +0300"
export GIT_COMMITTER_DATE="2024-03-11 15:00:00 +0300"
git revert --no-edit HEAD
```

```
[main 5bba728] Reapply "Arama çıktısını yalnızca Türkçe sütuna indir"
 Date: Mon Mar 11 15:00:00 2024 +0300
 1 file changed, 1 insertion(+), 1 deletion(-)
```

This time the message starts with `Reapply`, not `Revert`: reverting a revert is, in
essence, reapplying.

```bash
git log --format='%h %T %s' -n 4
```

```
5bba728 0f2d862b8a585d8dfc9b1a0acbdb30a221233ade Reapply "Arama çıktısını yalnızca Türkçe sütuna indir"
c497738 961039799e5ee6f157229351478a98abb7652c82 Revert "Arama çıktısını yalnızca Türkçe sütuna indir"
d245350 0f2d862b8a585d8dfc9b1a0acbdb30a221233ade Arama çıktısını yalnızca Türkçe sütuna indir
377b1f8 961039799e5ee6f157229351478a98abb7652c82 README'ye kullanım bölümü ekle
```

Four commits alternate back and forth between two trees. This last step was not kept in
the example repository's history — the flawed change was not reapplied; the revert was
left standing. To keep the example repository in that state, the last commit is rewound:

```bash
git reset --hard HEAD~1
```

```
HEAD is now at c497738 Revert "Arama çıktısını yalnızca Türkçe sütuna indir"
```

#### Reverting or Resetting

| Criterion | Reverting | Resetting |
|---|---|---|
| History | Preserved, a new commit is added | Rewritten |
| On a shared commit | Suitable | Not suitable |
| Visibility of the mistake | Stays on record | Erased |
| Which commit it applies to | Any commit | Backward from the branch tip |

There is a single criterion: if the commit has been shared, revert; if not, either can
be used.

#### Summary

- Reverting creates a new commit that applies the reverse of another commit's diff.
- History is preserved; no object changes, only a new commit is added.
- A revert commit's tree is the same as the tree before the reverted commit; this
  equality is a verification tool.
- The `-n` option puts the result in the staging area without committing it; `--abort`
  cancels the operation.
- A revert can be reverted too; the result is the change being reapplied.

#### Next Step

The tools covered so far were about completed changes. So what happens when there is
unfinished work and it becomes necessary to switch to something else? Committing is
premature, and undoing is a loss. The next lesson takes up the stash, which sets
unfinished work aside temporarily, and what that storage corresponds to in the object
model.

### Stash

Source: https://academia.sh/en/courses/introduction-to-version-control/stash

Temporarily setting aside unfinished work; the stash stack, inspecting its content, including untracked files, and what the stash corresponds to in the object model.

The tools covered so far were about completed changes: a change is either committed or
undone. There is a third situation — the change is unfinished, and it becomes necessary
to switch to other work. Committing is premature, because there is no working whole yet;
undoing is a loss.

The **stash** is a holding area defined for exactly this situation: the working directory
and the staging area are cleaned, their content is set aside, and it is brought back
later.

#### Unfinished Work

An option that lists all the terms is being added to the search script. The usage line
has been updated, the implementation has not been written yet; an untracked file has
also been created as a to-do list. To set this state up, the usage line is changed and the to-do
file is written:

```bash
sed -i.bak 's/ara.sh ARANAN/ara.sh ARANAN | ara.sh -l/' ara.sh
rm ara.sh.bak
echo "- [ ] -l seçeneğini gerçekleştir" > TODO.md
```

```bash
git status --short
```

```
 M ara.sh
?? TODO.md
```

Meanwhile a gap in the documentation is reported, and it needs to be fixed first. The
unfinished work cannot be committed.

#### Putting Work in the Stash

A stash entry is a commit too and carries a timestamp like one; so that your outputs stay the same as
in this lesson, the date is written first (the date variables stay defined in the open session; retype
them if you opened a new terminal):

```bash
export GIT_AUTHOR_DATE="2024-03-12 09:30:00 +0300"
export GIT_COMMITTER_DATE="2024-03-12 09:30:00 +0300"
git stash push -m "listeleme seçeneği, yarım"
```

```
Saved working directory and index state On main: listeleme seçeneği, yarım
```

```bash
git status --short
```

```
?? TODO.md
```

The change in the tracked file disappeared; the working directory returned to its state
at the last commit. The untracked `TODO.md` stayed in place: by default, the stash takes
only tracked files.

The `-m` option attaches a description to the entry. If no description is given, the
last commit's subject line is put in its place; once multiple entries pile up, these
descriptions stop being distinctive.

#### The Stash Is a Stack

```bash
git stash list
```

```
stash@{0}: On main: listeleme seçeneği, yarım
```

Entries behave like a stack: the most recently added one becomes `stash@{0}`, earlier
entries shift back by one position. The last-in-first-out order defined in the Data
Structures course is applied here directly. Access is not limited to the stack, though:
any entry can be named by its index number and taken or deleted on its own.

#### Inspecting the Content

```bash
git stash show
```

```
 ara.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
```

```bash
git stash show -p
```

```
diff --git a/ara.sh b/ara.sh
index 582ddde..078ea08 100755
--- a/ara.sh
+++ b/ara.sh
@@ -2,7 +2,7 @@
 # terimler.txt içinde arama yapar.
 
 if [ $# -ne 1 ]; then
-    echo "kullanım: ara.sh ARANAN" >&2
+    echo "kullanım: ara.sh ARANAN | ara.sh -l" >&2
     exit 1
 fi
 
```

The second command gives the full diff. If no entry is specified, `stash@{0}` is
assumed; for another entry, its name is written.

#### Interrupting Work

Because the working directory is clean, the urgent fix is made comfortably and
committed:

```bash
cat >> README.md <<'END'

Arama büyük/küçük harfe duyarlı değildir.
END
export GIT_AUTHOR_DATE="2024-03-12 09:48:00 +0300"
export GIT_COMMITTER_DATE="2024-03-12 09:48:00 +0300"
git commit -a -m "Aramanın harf duyarsızlığını README'de belirt"
```

```
[main 11fe7e8] Aramanın harf duyarsızlığını README'de belirt
 1 file changed, 2 insertions(+)
```

#### Bringing It Back

```bash
git stash pop
```

```
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   ara.sh

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	TODO.md

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (f319b5815214cb386c16d3fe061c284d6ff9936d)
```

The unfinished work came back, and the stash entry was deleted. The last line reports
the deleted entry's object ID; if this ID is saved, the entry can be brought back even
if it is deleted by mistake.

The distinction between the two commands matters:

| Command | Effect |
|---|---|
| `git stash apply` | Applies the content, leaves the entry in the list |
| `git stash pop` | Applies the content and drops the entry from the list |
| `git stash drop` | Deletes the entry without applying it |
| `git stash clear` | Deletes every entry |

Because a conflict can arise while applying, `apply` is preferred in cases where the
outcome is uncertain: if the application fails, the entry is still in the list.

#### Including Untracked Files

```bash
export GIT_AUTHOR_DATE="2024-03-12 10:05:00 +0300"
export GIT_COMMITTER_DATE="2024-03-12 10:05:00 +0300"
git stash push -u -m "listeleme seçeneği, yarım"
```

```
Saved working directory and index state On main: listeleme seçeneği, yarım
```

```bash
git status --short
```

The output is empty; `TODO.md` has been stashed too. The `-u` option covers untracked
files as well. Ignored files need a separate option; the default behavior leaves them in
place.

#### What the Stash Is in the Object Model

The stash is not a separate data structure. It is a ref named `refs/stash`, and what it
points to is a commit:

```bash
git rev-parse stash@{0}
```

```
48acf5295ce885fe839291522c1d8e48d7ad73e1
```

```bash
git cat-file -p stash@{0}
```

```
tree fa90a8ed35bd3b7bad8a3f7048186a030b33df2e
parent 11fe7e8840e866dda55da81f522f27f3268f1751
parent 22beea37c86e26d1cbe008233684b5d656b87945
parent f5310757c803a7c475d9ee8c0a7f972b8db76418
author Deniz Kaya <deniz@ornek.test> 1710227100 +0300
committer Deniz Kaya <deniz@ornek.test> 1710227100 +0300

On main: listeleme seçeneği, yarım
```

There are three parent lines. The first is the branch tip at the moment the stash was
taken; the second is a commit storing the staging area's state at that moment; the
third, because `-u` was used, is a commit storing the untracked files. Without `-u`
there would have been two parents.

This structure answers the lesson's opening question at the level of the object model:
the stash is not "a temporary area" — it is a small piece of history made of ordinary
commits, just attached to no branch. Stash entries also stand in the object database,
and once they become unreachable, they fall within the scope of garbage collection.

#### Limits

The stash is local and is not shared; entries in one copy of the repository do not show
up in other copies. It carries no structure beyond descriptions: entries are neither
sorted nor grouped. For this reason it is not suitable for long-term holding — which
piece of work an accumulated entry belongs to becomes unclear before long.

If unfinished work needs to be held for days, the right tool is not the stash but
commits written on a separate branch. Branches are the subject of the Branching and
Collaboration course.

In the example repository, the entry is brought back:

```bash
git stash pop
```

```
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   ara.sh

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	TODO.md

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (48acf5295ce885fe839291522c1d8e48d7ad73e1)
```

The option is then completed: the script is rewritten so that it prints only the Turkish column when
`-l` is given, `TODO.md` is deleted, and the change is committed:

```bash
cat > ara.sh <<'END'
#!/bin/sh
# terimler.txt içinde arama yapar.

if [ $# -ne 1 ]; then
    echo "kullanım: ara.sh ARANAN | ara.sh -l" >&2
    exit 1
fi

if [ "$1" = "-l" ]; then
    cut -d '|' -f 1 terimler.txt
    exit 0
fi

if ! grep -i -- "$1" terimler.txt; then
    echo "eşleşme yok: $1" >&2
    exit 2
fi
END
rm TODO.md
export GIT_AUTHOR_DATE="2024-03-12 10:20:00 +0300"
export GIT_COMMITTER_DATE="2024-03-12 10:20:00 +0300"
git commit -a -m "Tüm terimleri listeleyen -l seçeneğini ekle"
```

```
[main a0e3bfe] Tüm terimleri listeleyen -l seçeneğini ekle
 1 file changed, 6 insertions(+), 1 deletion(-)
```

#### Summary

- The stash sets aside changes in tracked files and returns the working directory to
  its state at the last commit.
- Entries are held in stack order; `stash@{0}` is the most recently added.
- `apply` leaves the entry in the list, `pop` drops it, `drop` deletes it without
  applying it.
- The `-u` option covers untracked files too.
- A stash entry is a commit with two or three parents, attached to no branch.

#### Next Step

Specific points in history have not been given human-readable names; everything has
been referred to by forty-character IDs or by relative notation. When a release is
published, that point needs a permanent name. The course's last lesson takes up tags,
the fourth object type, and their relationship with semantic versioning.

### Tagging

Source: https://academia.sh/en/courses/introduction-to-version-control/tagging

Giving permanent names to specific points in history; the difference between a lightweight tag and a tag object, the fourth object type, locating position by tag, and semantic versioning.

Up to this point, points in history have been referred to by forty-character IDs or by
relative notation like `HEAD~1`. Neither is a permanent name: an ID cannot be kept in
mind, and relative notation points at a different commit as history grows.

A **tag** is a fixed name given to a specific commit. Its difference from branch tips is
that it does not budge when a new commit is written: the branch advances, the tag stays.

#### Lightweight Tag

The plainest form binds a commit ID to a name:

```bash
git tag v1.0.0 HEAD~1
git tag -l
```

```
v1.0.0
```

```bash
cat .git/refs/tags/v1.0.0
```

```
11fe7e8840e866dda55da81f522f27f3268f1751
```

The file's content is directly the commit ID. A **lightweight tag** creates no new
object in the object database; it only writes a ref file.

```bash
git cat-file -t v1.0.0
```

```
commit
```

The name points at a commit. Who placed the tag, when, and why is recorded nowhere.
This is insufficient for marking a published release.

```bash
git tag -d v1.0.0
```

```
Deleted tag 'v1.0.0' (was 11fe7e8)
```

#### The Tag Object

```bash
export GIT_COMMITTER_DATE="2024-03-13 09:00:00 +0300"
git tag -a v1.0.0 -m "İlk kararlı sürüm: arama, harf duyarsızlığı, hata kodu" HEAD~1
git cat-file -t v1.0.0
```

```
tag
```

The object type changed. An **annotated tag** is the last of the four object types
introduced in the second topic, and it is written to the object database:

```bash
git cat-file -p v1.0.0
```

```
object 11fe7e8840e866dda55da81f522f27f3268f1751
type commit
tag v1.0.0
tagger Deniz Kaya <deniz@ornek.test> 1710309600 +0300

İlk kararlı sürüm: arama, harf duyarsızlığı, hata kodu
```

There are five components: the ID and type of the pointed-at object, the tag name, the
person who placed the tag together with the time, and the description text. The
structure resembles a commit object — and it is named by the same rule:

```bash
git rev-parse v1.0.0
```

```
9c57fc4649ef6bdcd4ee73dc697fe500468d50f6
```

This ID belongs to the tag object, not the commit. To reach the pointed-at commit, a
type conversion is requested:

```bash
git rev-parse "v1.0.0^{commit}"
```

```
11fe7e8840e866dda55da81f522f27f3268f1751
```

Most commands perform this conversion on their own; the distinction shows up only when
the object itself is asked for directly.

```bash
git show v1.0.0 --stat
```

```
tag v1.0.0
Tagger: Deniz Kaya <deniz@ornek.test>
Date:   Wed Mar 13 09:00:00 2024 +0300

İlk kararlı sürüm: arama, harf duyarsızlığı, hata kodu

commit 11fe7e8840e866dda55da81f522f27f3268f1751
Author: Deniz Kaya <deniz@ornek.test>
Date:   Tue Mar 12 09:48:00 2024 +0300

    Aramanın harf duyarsızlığını README'de belirt

 README.md | 2 ++
 1 file changed, 2 insertions(+)
```

The output shows the tag first, then the commit it points at.

| Criterion | Lightweight tag | Annotated tag |
|---|---|---|
| Creates an object | No | Yes |
| Records who and when | No | Yes |
| Description text | No | Yes |
| Use | Temporary, local marker | Published release |

#### Listing Tags

After the listing option is finished, a second release is tagged. First the option is documented
in the README and committed:

```bash
cat >> README.md <<'END'

Tüm terimleri listelemek için `./ara.sh -l` yazın.
END
export GIT_AUTHOR_DATE="2024-03-13 11:00:00 +0300"
export GIT_COMMITTER_DATE="2024-03-13 11:00:00 +0300"
git commit -a -m "README'de -l seçeneğini belgele"
```

Then the tag is placed:

```bash
export GIT_COMMITTER_DATE="2024-03-13 11:10:00 +0300"
git tag -a v1.1.0 -m "Listeleme seçeneği (-l) eklendi"
git tag -l
```

```
v1.0.0
v1.1.0
```

```bash
git tag -l "v1.0*"
```

```
v1.0.0
```

```bash
git tag -n
```

```
v1.0.0          İlk kararlı sürüm: arama, harf duyarsızlığı, hata kodu
v1.1.0          Listeleme seçeneği (-l) eklendi
```

Tags also show up in the history list:

```bash
git log --oneline --decorate -n 5
```

```
67c7ca6 (HEAD -> main, tag: v1.1.0) README'de -l seçeneğini belgele
a0e3bfe Tüm terimleri listeleyen -l seçeneğini ekle
11fe7e8 (tag: v1.0.0) Aramanın harf duyarsızlığını README'de belirt
c497738 Revert "Arama çıktısını yalnızca Türkçe sütuna indir"
d245350 Arama çıktısını yalnızca Türkçe sütuna indir
```

All the refs can also be listed with a single command:

```bash
git for-each-ref refs/tags
```

```
9c57fc4649ef6bdcd4ee73dc697fe500468d50f6 tag	refs/tags/v1.0.0
7cbb6f6054352e79e94cf24d5079ae5af15482f1 tag	refs/tags/v1.1.0
```

The second column gives the object type; for lightweight tags, this column would read
`commit`.

#### Locating Position by Tag

```bash
git describe
```

```
v1.1.0
```

When a new commit is written after the tag, the output changes. A compound-term example is added to
the README and committed:

```bash
cat >> README.md <<'END'

Bileşik terimleri tırnak içinde verin: `./ara.sh "bağlı liste"`.
END
export GIT_AUTHOR_DATE="2024-03-14 10:00:00 +0300"
export GIT_COMMITTER_DATE="2024-03-14 10:00:00 +0300"
git commit -a -m "Bileşik terim örneği ekle"
```

```bash
git describe
```

```
v1.1.0-1-g2a41365
```

There are three parts: the nearest tag, the number of commits written since that tag,
and the current commit's shortened ID. The `g` at the start of the ID marks the value as
a git object. When this string is embedded in an executable program's version
information, it says exactly which commit the binary in hand was built from.

#### Semantic Versioning

A tag's name is free; but there is a common scheme for marking releases: **semantic
versioning**. A version number consists of three numbers separated by dots, and
incrementing each number makes a promise:

| Part | When it increments | What it tells the user |
|---|---|---|
| Major | When backward compatibility breaks | Adaptation may be needed |
| Minor | When a compatible new capability is added | Existing use is not broken |
| Patch | When only a fix is made | Behavior is the same, a flaw was fixed |

In the example repository, `v1.0.0` marked the first stable release, and `v1.1.0`
marked the addition of a backward-compatible capability: the `-l` option does not break
previous use, it only adds a new form of invocation. If the script's exit codes had been
changed, the major version would have needed to increment.

The scheme's value lies less in the number itself than in the promise it makes. The
concept of backward compatibility was defined in the Programming Fundamentals course, in
the context of language choice; a version number is a machine-readable declaration of
that same concept.

#### The Limits of Tags

Tags can be moved, but they should not be. If a tag is shifted to another commit after
being published, copies that already have that tag keep pointing at the old commit; the
same name ends up naming two different pieces of content. The point a published tag
points at is not changed; a new tag is placed instead, if one is needed.

Deletion carries the same problem. A tag deleted locally keeps standing in other copies
of the repository.

Tags can also point at objects other than commits — a tag can be placed on a blob or a
tree. In practice this is rarely used; a tag's usual target is a commit.

#### Summary

- A tag is a fixed name given to a commit, one that does not get displaced by new
  commits.
- A lightweight tag is only a ref file; an annotated tag is a separate object storing
  who placed it and a description.
- The tag object is the fourth object type, and it is named by the same rule as the
  others.
- `git describe` gives the number of commits since the nearest tag and the current ID.
- Semantic versioning promises backward compatibility with a three-number version.
- Published tags are not moved or deleted; a new tag is placed when needed.

#### Course Wrap-Up

A single repository was built across the course, and it reached its present state
through eighteen commits:

```bash
git log --oneline --decorate
```

```
2a41365 (HEAD -> main) Bileşik terim örneği ekle
67c7ca6 (tag: v1.1.0) README'de -l seçeneğini belgele
a0e3bfe Tüm terimleri listeleyen -l seçeneğini ekle
11fe7e8 (tag: v1.0.0) Aramanın harf duyarsızlığını README'de belirt
c497738 Revert "Arama çıktısını yalnızca Türkçe sütuna indir"
d245350 Arama çıktısını yalnızca Türkçe sütuna indir
377b1f8 README'ye kullanım bölümü ekle
0e74ba9 Öbek ve dizi terimlerini ekle
d3ab7b0 Yerel notları izlemeden çıkar
7e44d29 Yerel notları ekle
9b23cc3 Üretilen dosyaları yoksayma listesine al
ece664b Küme terimini ekle
2c883a7 Eşleşme bulunamadığında hata bildir
60e13bd Aramayı büyük/küçük harften bağımsız yap
43370ca Üç veri yapısı terimi ekle
3132c78 Biçim örneğindeki yazımı düzelt
546c174 Terim arama betiği ekle
13d31bf Terim listesini ve biçim belgesini ekle
```

This list shows the course's structure: files being added, atomic commits, a mistake
being committed and reverted, ignore rules being set up, and two releases being tagged.

The course's real subject was not the commands but the model underneath them. Four
object types — blob, tree, commit, tag — are named by content-addressed storage;
commits build a directed acyclic graph through their parent links; the staging area
accumulates the next snapshot to be added to that graph. Every command is a consequence
of these three sentences. Why amending produces a new ID, why reverting does not disturb
history, why the same content gives the same ID — the answer to all three is in the
model.

One subject was deliberately left out across the course: history stayed a single chain
throughout. Yet the graph's definition allowed branching, and `--graph`'s output coming
out as a single column was not a limitation — it was a property of this particular
repository.

The next course — **Branching and Collaboration** — multiplies that column. It takes up
the fact that a branch is nothing but a pointer, how two branches are merged, how
conflicts are resolved, and how history is synced with remote repositories. The model
built in this course holds there too: a branch is nothing but a file under `refs/heads`;
a merge is nothing but a commit with two parents.

