linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Chapters
  • How it works
  • Lessons
  • Knowledge base
  • Interview prep
home/git/lessons/git-lab-03-1-manual-commit

lesson ── git-labs ── ~20 мин ── 8 шагов

Build a commit by hand with plumbing commands

The goal is to see by hand that Git is a key-value object store on top of the file system. You'll build a complete commit without git add and git commit, using only plumbing: hash-object, write-tree, commit-tree, update-ref. Once you see that porcelain is just a wrapper, the fear of Git goes away.

▶ интерактивный sandbox

Поднимется контейнер gitlab/git-base с git, bash, pre-commit. В браузере откроется терминал, можно сразу git init. Каждый шаг проверяется автоматически. Сеть air-gapped, github.com недоступен.

запустить sandbox →

stack ── git · bash · 256 MB RAM · air-gapped · самоуничтожается через 30 мин простоя

Шаги

  1. 01

    Create an empty repo

    bash
    cd /home/student/work
    mkdir -p plumbing && cd plumbing
    git init -b main             # -b main = default branch is main, not master
    ls -la .git/                 # see the repo's internal structure

    Inside .git/, look at objects/ and refs/. Right now they're almost empty. Over the course of the lesson you'll fill them yourself.

    ✓ Repo initialized. objects/ is empty.

  2. 02

    Write file content as a blob

    hash-object -w takes text and writes it into .git/objects/, returning a SHA. The file itself does not appear in the working tree.

    bash
    echo "Hello, Git internals" > /tmp/payload
    SHA=$(git hash-object -w /tmp/payload)    # -w writes the blob into the object store, returns the SHA
    echo "blob: $SHA"
    ls .git/objects/${SHA:0:2}/               # ${SHA:0:2} = first 2 characters of the SHA (folder name)

    A file appeared inside .git/objects/aa/bbcc.... That is the blob: zlib-compressed content.

    подсказка

    A SHA is 40 hex characters. The first 2 are the folder name, the rest are the file name.

    ✓ The blob is in the object store. It has no name yet.

  3. 03

    Read the blob back with cat-file

    An object has no file name, only a SHA. To read its content:

    bash
    cd /home/student/work/plumbing
    # the awk one-liner joins folder name + file name = the object's full SHA
    SHA=$(find .git/objects -type f | head -1 | awk -F/ '{print $(NF-1)$NF}')
    git cat-file -t $SHA          # -t = object type (blob/tree/commit/tag)
    git cat-file -p $SHA          # -p = pretty-print the content

    -t shows the object type (blob), -p shows the content. This is the first layer of Git: content-addressed storage.

    ✓ The object is a blob and holds your string.

  4. 04

    Put the blob into the index under a name

    A blob by itself is anonymous. For it to become a "file" with a name, you need an entry in the index (the future tree).

    bash
    cd /home/student/work/plumbing
    SHA=$(find .git/objects -type f | head -1 | awk -F/ '{print $(NF-1)$NF}')
    # --cacheinfo <mode> <sha> <path>: put a ready blob into the index under a name
    git update-index --add --cacheinfo 100644 $SHA hello.txt
    git ls-files --stage          # see the contents of the index

    100644 is the mode for a regular file. git ls-files --stage shows the contents of the index: SHA + name.

    ✓ The index knows: hello.txt = your blob.

  5. 05

    Build a tree from the current index

    write-tree takes the state from the index and materializes it as a tree object.

    bash
    cd /home/student/work/plumbing
    TREE=$(git write-tree)        # reads the index, writes a tree object, returns its SHA
    echo "tree: $TREE"
    git cat-file -p $TREE         # see the tree's content (a list of entries)

    The result is a table: 100644 blob <sha> hello.txt. That is the tree: a list of entries.

    ✓ The tree is built. Inside it is the entry for hello.txt.

  6. 06

    Create a commit object with commit-tree

    A commit is tree + message + author + (optionally) a parent. Since this is the first commit, there is no parent.

    bash
    cd /home/student/work/plumbing
    TREE=$(git write-tree)
    # commit-tree <tree-sha>: create a commit, reading the message from stdin
    # (no -p here because this is the first commit, there is no parent)
    COMMIT=$(echo "First commit by hand" | git commit-tree $TREE)
    echo "commit: $COMMIT"
    git cat-file -p $COMMIT       # see the commit's fields

    You see the fields: tree <sha>, author ... <email>, committer ..., a blank line, the message. That's everything in a commit.

    ✓ The commit object is created. It has a SHA, but no branch knows about it yet.

  7. 07

    Move the main branch onto this commit

    Right now git log is empty: HEAD points to main, but main does not exist. Create the ref.

    bash
    cd /home/student/work/plumbing
    TREE=$(git write-tree)
    COMMIT=$(echo "First commit by hand" | git commit-tree $TREE)
    # update-ref <ref> <sha>: atomically write the SHA into the branch file
    git update-ref refs/heads/main $COMMIT
    git log --oneline             # now log is not empty: main exists
    cat .git/refs/heads/main      # a branch is an ordinary text file with a SHA

    git log now shows your commit. .git/refs/heads/main is an ordinary text file with a SHA.

    подсказка

    If the log is empty, make sure HEAD points to main: `cat .git/HEAD`.

    ✓ The branch is moved, the commit shows in the log. You built a commit by hand.

  8. 08

    Run a checkout so the file appears in the working tree

    Right now the file hello.txt is not physically in the working tree: it's only in objects + index. Check:

    bash
    cd /home/student/work/plumbing
    ls                                  # empty: hello.txt exists only in the objects
    git checkout HEAD -- hello.txt      # pull the blob from the object store and put it on disk
    cat hello.txt                       # here's the same content as in the blob

    checkout pulled the blob from the object store and put it on disk. The cycle is closed: content -> blob -> index -> tree -> commit -> ref -> checkout -> file on disk.

    ✓ The file is in place. Next, in chapter 4, you'll go through this same path bottom-up, reading existing objects.

Что ты узнал

Git stores three object types: blob (content), tree (directory), commit (snapshot + metadata). Each object is addressed by the SHA-1 of its content. Plumbing commands give you direct access to all three layers.

команды

  • git hash-object -w filewrite a blob into the object store
  • git update-index --add --cacheinfo 100644 <sha> pathput a blob into the index
  • git write-treebuild a tree from the current index
  • git commit-tree <tree> -m '...'create a commit object
  • git update-ref refs/heads/main <sha>move a branch onto a commit

концепции

  • · blob = file content without a name
  • · tree = table of entries name -> mode + sha
  • · commit = a pointer to tree + parent + author + message

следующая →

Reconstruct the log by hand with cat-file

Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies