lesson ── git-labs ── ~20 мин ── 8 шагов
Цель - увидеть руками, что Git это key-value хранилище объектов поверх
файловой системы. Соберёшь полный коммит без git add и git commit,
только через plumbing: hash-object, write-tree, commit-tree,
update-ref. Когда увидишь, что porcelain - это просто обёртка,
страх перед Git проходит.
интерактивный 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 && cd plumbing
git init -b main # -b main = ветка по умолчанию main, не master
ls -la .git/ # увидеть внутреннюю структуру репо
Внутри .git/ посмотри на objects/ и refs/. Сейчас они почти
пусты. По ходу урока ты их заполнишь сам.
✓ Репо инициализирован. objects/ пустой.
hash-object -w берёт текст и записывает его в .git/objects/,
возвращая SHA. Сам файл в working tree не появляется.
echo "Hello, Git internals" > /tmp/payload
SHA=$(git hash-object -w /tmp/payload) # -w записывает blob в object store, возвращает SHA
echo "blob: $SHA"
ls .git/objects/${SHA:0:2}/ # ${SHA:0:2} = первые 2 символа SHA (имя папки)Внутри .git/objects/aa/bbcc... появился файл. Это и есть blob -
zlib-сжатое содержимое.
SHA из 40 hex-символов. Первые 2 - имя папки, остальные - имя файла.
✓ Blob лежит в object store. Имени у него пока нет.
У объекта нет имени файла - только SHA. Прочесть содержимое:
cd /home/student/work/plumbing
# awk-однострочник склеивает имя папки + имя файла = полный SHA объекта
SHA=$(find .git/objects -type f | head -1 | awk -F/ '{print $(NF-1)$NF}')git cat-file -t $SHA # -t = тип объекта (blob/tree/commit/tag)
git cat-file -p $SHA # -p = pretty-print содержимого
-t показывает тип объекта (blob), -p - содержимое. Это первый
слой Git: контент-адресованное хранилище.
✓ Объект - blob, содержит твою строку.
Blob по себе - анонимный. Чтобы он стал «файлом» с именем, нужна запись в index (tree-будущее).
cd /home/student/work/plumbing
SHA=$(find .git/objects -type f | head -1 | awk -F/ '{print $(NF-1)$NF}')# --cacheinfo <mode> <sha> <path>: положить готовый blob в index под именем
git update-index --add --cacheinfo 100644 $SHA hello.txt
git ls-files --stage # увидеть содержимое index
100644 - mode regular file. git ls-files --stage показывает
содержимое index: SHA + имя.
✓ Index знает: hello.txt = твой blob.
write-tree берёт state из index и материализует его как tree-объект.
cd /home/student/work/plumbing
TREE=$(git write-tree) # читает index, пишет tree-объект, возвращает его SHA
echo "tree: $TREE"
git cat-file -p $TREE # увидеть содержимое tree (список записей)
Результат - таблица: 100644 blob <sha> hello.txt. Это и есть
tree: список записей.
✓ Tree сформирован. Внутри - запись на hello.txt.
Commit - это tree + сообщение + автор + (опционально) parent. Поскольку это первый коммит, parent'а нет.
cd /home/student/work/plumbing
TREE=$(git write-tree)
# commit-tree <tree-sha>: сделать commit, читая сообщение со stdin
# (без -p тут потому что это первый коммит, parent'а нет)
COMMIT=$(echo "Первый коммит руками" | git commit-tree $TREE)
echo "commit: $COMMIT"
git cat-file -p $COMMIT # увидеть поля commit'а
Видишь поля: tree <sha>, author ... <email>, committer ...,
пустая строка, сообщение. Это всё, что в commit'е.
✓ Commit-объект создан. У него есть SHA, но ветка про него ещё не знает.
Сейчас git log пуст: HEAD указывает на main, а main не существует.
Создай ref.
cd /home/student/work/plumbing
TREE=$(git write-tree)
COMMIT=$(echo "Первый коммит руками" | git commit-tree $TREE)
# update-ref <ref> <sha>: атомарно записать SHA в файл ветки
git update-ref refs/heads/main $COMMIT
git log --oneline # теперь log не пуст - main существует
cat .git/refs/heads/main # ветка - это обычный текстовый файл с SHA
git log теперь показывает твой коммит. .git/refs/heads/main -
обычный текстовый файл с SHA.
Если log пустой - убедись что HEAD указывает на main: `cat .git/HEAD`.
✓ Ветка двинута, коммит виден в логе. Ты собрал коммит вручную.
Сейчас файла hello.txt физически в working tree нет - он только
в objects + index. Проверь:
cd /home/student/work/plumbing
ls # пусто - hello.txt существует только в объектах
git checkout HEAD -- hello.txt # достать blob из object store и положить на диск
cat hello.txt # вот тот же контент, что и в blob'е
checkout достал blob из object store и положил его на диск.
Цикл замкнут: контент -> blob -> index -> tree -> commit -> ref ->
checkout -> файл на диске.
✓ Файл на месте. Дальше - в главе 4 пройдёшь это же снизу вверх, читая существующие объекты.
Git хранит три типа объектов: blob (содержимое), tree (директория), commit (снимок + метаданные). Каждый объект адресуется SHA-1 от его содержимого. Plumbing-команды дают прямой доступ ко всем трём слоям.
команды
git hash-object -w fileзаписать blob в object storegit update-index --add --cacheinfo 100644 <sha> pathположить blob в indexgit write-treeслепить tree из текущего indexgit commit-tree <tree> -m '...'сделать commit-объектgit update-ref refs/heads/main <sha>двинуть ветку на commitконцепции