lesson ── git-labs ── ~20 мин ── 7 шагов
The goal is to walk Git from the bottom up. You build a repo of a few
commits with ordinary commands, then reach by hand from the branch to the
commit, to the tree, to the blob, using only cat-file. Once you do this
one time, every other plumbing command reads as obvious.
интерактивный sandbox
Поднимется контейнер gitlab/git-base с git, bash, pre-commit. В браузере откроется терминал, можно сразу git init. Каждый шаг проверяется автоматически. Сеть air-gapped, github.com недоступен.
stack ── git · bash · 256 MB RAM · air-gapped · самоуничтожается через 30 мин простоя
cd /home/student/work
mkdir -p plumbing-log && cd plumbing-log
git init -b main
echo "v1" > version.txt
git add . && git commit -m "init"
echo "v2" > version.txt
git commit -am "bump to v2" # -a = stage already-tracked files, -m = inline message
mkdir docs
echo "# readme" > docs/README.md
git add . && git commit -m "add docs"
git log --oneline # --oneline = one line per commit (short-sha + subject)
Three commits: the second changes a file, the third adds a directory. Next you read all of this by hand.
✓ Three commits in place. Ready to dig.
A branch is just a text file with a SHA. Read it:
cd /home/student/work/plumbing-log
cat .git/refs/heads/main
Compare it with git rev-parse HEAD: the same SHA. So git rev-parse
is cat plus resolving symbolic refs.
✓ The HEAD SHA is found. It points to the last commit.
cd /home/student/work/plumbing-log
SHA=$(cat .git/refs/heads/main) # ref is a text file with a SHA, not magic
git cat-file -t $SHA # -t = object type (commit)
git cat-file -p $SHA # -p = pretty-print: tree, parent, author, message
The type is commit. Inside are the fields tree <sha>, parent <sha>,
author, committer, and the message. Remember the tree-SHA and the
parent-SHA; you need them.
Capture the tree-SHA: `TREE=$(git cat-file -p $SHA | head -1 | cut -d' ' -f2)`.
✓ The commit reads. You can see the tree and the parent.
cd /home/student/work/plumbing-log
SHA=$(cat .git/refs/heads/main)
# the first output line is "tree <sha>", cut -f2 takes the second field
TREE=$(git cat-file -p $SHA | head -1 | cut -d' ' -f2)
git cat-file -t $TREE # should be "tree"
git cat-file -p $TREE # list of entries: mode type sha name
The tree holds two entries: version.txt (a blob) and docs (another
tree). Look at the mode column: 100644 for a file, 040000 for a
subdirectory.
✓ The tree is open. You can see the file and the subdirectory.
cd /home/student/work/plumbing-log
SHA=$(cat .git/refs/heads/main)
TREE=$(git cat-file -p $SHA | head -1 | cut -d' ' -f2)
# awk '{print $3}' - tree-entry format is "<mode> <type> <sha> <name>", sha is the 3rd columnBLOB=$(git cat-file -p $TREE | grep 'version.txt' | awk '{print $3}')git cat-file -p $BLOB # content of version.txt
It should print v2. Compare it with the working tree: cat version.txt.
The same content, just a different path to it.
✓ The blob is read. This is the final leaf of the object tree.
The link between commits is the parent field. Step back by hand:
cd /home/student/work/plumbing-log
SHA=$(cat .git/refs/heads/main)
# find the "parent <sha>" line in the commit, take the sha (2nd field)
PARENT=$(git cat-file -p $SHA | grep '^parent' | head -1 | cut -d' ' -f2)
git cat-file -p $PARENT # this is the second commit from the log ("bump to v2")This is the second commit from the log. Its tree is different: there
version.txt holds v2 and there is no docs/. Each commit is a
snapshot, not a diff.
✓ One step back done. Now you hold the whole graph in your head.
Put it all together. A script that walks from HEAD along parent and prints the SHA plus the first word of the message:
cd /home/student/work/plumbing-log
# <<'EOF' (quoted) = heredoc with no $ interpolation inside
cat > log.sh <<'EOF'
#!/bin/bash
cur=$(cat .git/refs/heads/main) # start from the branch tip
while [ -n "$cur" ]; do # while there is a parent, keep going
msg=$(git cat-file -p $cur | tail -1) # the last line of the commit is the message body
echo "${cur:0:7} $msg" # ${cur:0:7} = short-sha (first 7 hex)cur=$(git cat-file -p $cur | grep '^parent' | head -1 | cut -d' ' -f2)
done
EOF
chmod +x log.sh
./log.sh
git log --oneline # compare the output with your script: they should match
Compare the outputs. They should match line by line (down to the short-sha).
✓ Your own git log is ready. The internals are no longer magic.
git cat-file -t gives an object's type, -p pretty-prints it.
The chain: ref -> commit -> tree -> blob. Each link is one SHA.
команды
cat .git/refs/heads/mainthe SHA of the branch's current commitgit cat-file -p <commit-sha>print the commit (see the tree-sha)git cat-file -p <tree-sha>print the tree (see the blob-sha)git cat-file -p <blob-sha>see the file's contentконцепции