Lesson 05 / 15
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.
Contents
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
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.
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:
git cat-file -p HEAD
tree 378e434005c6c42021d4398a15e89149b0fd8671 author Deniz Kaya <[email protected]> 1709532720 +0300 committer Deniz Kaya <[email protected]> 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.authorandcommitter— 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
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.
find .git/objects -type f
.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:
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.
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.
#!/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
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. The
working directory now has two unrelated changes:
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:
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:
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
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 committurns 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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.