You can create a minimal Git commit without git add or git commit: write a blob with git hash-object, assemble a tree with git mktree, create a commit with git commit-tree, then point a branch reference at it with git update-ref. This plumbing-level exercise shows what each object contains—and why a blob itself has no filename.
What Git stores: content, directory structure, and history
Git has four object types: blobs, trees, commits, and annotated tags. This walkthrough builds the first three. Git’s data model describes their roles:
- Blob: file content only. It does not store the filename or path.
- Tree: a directory listing that associates entry names and modes with blob or subtree object IDs.
- Commit: a pointer to a top-level tree, plus zero or more parent commit IDs, author and committer details and timestamps, and a message.
A root commit has no parent. Each object is identified by a hash of its type and content; changing the content or metadata creates a different object rather than modifying the original in place. A branch name is a ref that points to a commit—it is not part of the commit object.
How object IDs are formed
Git hashes an object’s type, its content length, a NUL byte, and the content itself. Hashing only the raw file bytes does not produce the Git blob ID. The Git hash-function transition document describes traditional SHA-1 object IDs as 40 hexadecimal characters and SHA-256 IDs as 64. The repository’s hash format determines the IDs used, so do not assume every repository uses SHA-1 or hard-code an ID length into generic instructions.
#1 Best Overall
Build a blob, tree, and root commit
Run this as a learning exercise in a new, isolated repository. The commands below are a recipe, not a claim that a particular run was tested; the resulting IDs depend on the exact content, identity, timestamps, and repository format.
- Create a repository and set the identity for this exercise.
mkdir git-object-lab cd git-object-lab git init git config user.name "Git Object Lab" git config user.email "[email protected]"Setting the identity locally avoids changing your global Git configuration. The email uses the reserved
.invaliddomain. Commit identity and timestamps become part of the commit contents; Git will supply timestamps when you create the commit.Rank #2
- Write file content as a blob.
blob=$(printf '%sn' 'Hello from a Git blob.' | git hash-object -w --stdin) printf '%sn' "$blob"--stdinreads the exact bytes supplied on standard input; the newline fromprintfis included. The default object type isblob, and-wwrites it to the repository’s object database. The command prints the object ID, which the shell stores inblob. See the git-hash-object manual. - Put the blob in a tree.
tree=$(printf '100644 blob %streadme.txtn' "$blob" | git mktree) printf '%sn' "$tree"The record uses a regular-file mode, the object kind
blob, the ID returned by the previous command, a tab, and the filename.git mktreeexpects thisls-tree-style format—not JSON or an ordinary shell listing. By default it checks that referenced objects exist and normalizes entry order. Use the actual blob ID;--missingexists but is not the normal route for this exercise. See the git-mktree manual.Recommended: Fix Windows Errors and Clear Junk Files in Minutes - Free Scan →Recommended: Crashes or Glitches? A Free Driver Scan Usually Finds the Culprit →Recommended: PC Feels Slow? A Free Scan Shows What's Dragging Windows Down →Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. - Create a root commit from that tree.
commit=$(git commit-tree "$tree" -m "Create a tree from a blob") printf '%sn' "$commit"Without a
-pparent option, this is a root commit. For a later commit, supply one or more parent IDs with-p. The command writes a commit object and prints its ID; it does not automatically move a branch. Git’s git-commit-tree manual cautions that this is “usually not what an end user wants to run directly.” For ordinary work, usegit addandgit commit. - Inspect the three objects.
git cat-file -t "$blob" git cat-file -p "$blob" git cat-file -t "$tree" git cat-file -p "$tree" git cat-file -t "$commit" git cat-file -p "$commit" git cat-file -e "$commit"-treports an object’s type,-pdisplays its contents in a readable form, and-echecks that the object exists. The blob shows the text, the tree shows its mode, kind, ID, and name, and the commit shows its tree, identity and timestamp headers, and message. Consult the git-cat-file manual. - Give the commit a branch name.
git update-ref refs/heads/main "$commit"This updates the
mainbranch ref to point to the commit. If you need to protect against changing a ref that has moved unexpectedly,git update-refaccepts an old object ID as an additional expected-value check. The exact form and behavior are documented in the git-update-ref manual.
What the inspection shows
The blob contains bytes, not a path
The blob’s payload is just the text supplied to hash-object. It does not know that the tree calls it readme.txt. The same blob can be referenced by different tree entries, including entries under different names.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Best Value
The tree supplies names and modes
A tree represents one directory. The regular-file mode in this example is 100644. Other modes you may encounter include 100755 for an executable file, 120000 for a symbolic link, 040000 for a directory/tree, and 160000 for a gitlink or submodule. A gitlink points to a commit object. A directory is represented by another tree, not by a blob containing a folder listing.
The commit connects a snapshot to history
The commit points to the top-level tree and records the identities and timestamps of the author and committer, the message, and parent IDs if present. Changing the tree, message, identities, timestamps, or parent changes the commit content and therefore its ID. An amended commit is a new object, not an in-place edit.
Why a commit can exist without appearing on a branch
git commit-tree writes an object, but it does not by itself make a branch point to that object. The separate git update-ref step supplies that convenient name. Refs are how Git gives movable names to objects, most commonly commits; branches move as new commits are added, while tags are generally used as stable names.
How this differs from everyday Git
The index, or staging area, is not a fifth object type. It is a separate staging format: Git normally turns the staged entries into tree object(s) when making a commit. This exercise skips the index by constructing a tree directly with git mktree. The official index-format documentation describes the index’s on-disk structure, including its header, entries, extensions, and checksum; you do not need to encode that format for this exercise.
These commands are plumbing: they expose low-level object and ref operations. The usual workflow is the higher-level porcelain of git add and git commit. For a broader explanation, Pro Git’s Git Internals chapter discusses Git objects. Its print edition, Pro Git Second Edition, was published in 2014; for current command flags, prefer the relevant Git manual.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




