Step 1 of 5 · Reading · ~3 min
Read
Trees & Commits
Commit Objects
A tree hash pins a complete project state, but a state alone is not history. The commit object adds the two missing dimensions: where this state came from (parent commits) and who made it, when and why (metadata and a message). After this lesson your program builds the exact bytes git commit writes and prints the commit's hash.
The format: finally, text
After binary trees a commit is a relief. Its payload is plain text:
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
author Test <[email protected]> 1000000000 +0000
committer Test <[email protected]> 1000000000 +0000
empty
That is a root commit of the empty directory by someone called Test, with a one-word message. Its payload is 132 bytes, so it is hashed as commit 132, a zero byte and these lines, and its name is 7d9f6b63fb41105af9452c6dcf55cf7613a78843. Line by line:
tree: exactly one, the snapshot this commit records. A commit stores no changes: git commits are snapshots, not diffs, and "what changed" is always computed by comparing two trees.parent: zero or more lines, in order. None for a root commit, one for an ordinary commit, two or more for a merge. History is nothing more than these links.authorandcommitter: who wrote the change and who created this commit. They are usually the same and differ after a rebase, a cherry-pick or a patch sent by email. Each readsName SP <email> SP timestamp SP timezone.- A blank line, then the message, verbatim.
The timestamp is seconds since 1970 in UTC. The timezone is the sign, two digits of hours and two of minutes, such as +0600 or -0330; it records the writer's local offset and is part of the hashed text but never changes the instant.
The hash chain
A commit's hash covers its whole text, and that text contains its parents' hashes, which contain theirs, all the way to the root. One 40-character commit name therefore pins the complete project state and the complete history behind it. Change anything in the past and every descendant gets a new name. That is why rewriting history (rebase) creates new commits instead of editing old ones, and why you can pin a build to one hash and trust it.
A practical consequence: the timestamp is inside the hashed bytes. Two commits of the same tree, parent and message, one second apart, have different names.
The message and its final newline
Git stores a message ending in exactly the newline that git commit adds. In this exercise you receive the message as typed; if it is not empty and does not end in a newline, add one. If it already ends with one (or two), keep what you were given.
Common mistakes
- Writing the parent lines in the wrong order, or one line holding several hashes. Each parent is its own
parentline, and the order is part of the hash. - Omitting the blank line, or putting two. Exactly one empty line separates headers from the message.
- Putting the timezone before the timestamp, or leaving out the angle brackets around the email.
- Counting characters instead of bytes for the header, when a name or message holds non-ASCII text. Zoë is five bytes.
- Dropping or doubling the final newline of the message.
Your exercise
The starter parses a commit description and hands you the pieces; you write commit_hash, which assembles the payload, wraps it in commit <byte length> and a zero byte, and returns the SHA-1. The expected hashes were produced by real git commit-tree from real trees and parents, so they match git byte for byte, including the merge and octopus cases.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…