Reading HEAD all the way to a commit means following the symbolic ref to a branch and the branch to an id, while handling the branch that does not exist yet. Today you build that resolution.
Resolve HEAD to the commit id it ultimately points at, or nothing if the branch is unborn.
Resolving a ref means chasing the pointers until you reach an id. HEAD usually
holds ref: refs/heads/main, so you follow that to the branch file and read the
commit id there. Two edges make this real. First, a brand-new repository has HEAD
pointing at main but no branch file yet, an unborn branch; resolving it
yields nothing rather than an error, which is exactly the state before the first
commit. Second, HEAD can hold a raw commit id instead of a ref: line, a
detached HEAD, and then it resolves to that id directly.
These two cases are the boundaries of ref resolution, and pinning them keeps the
logic honest: the happy path (HEAD to branch to id) is easy, but the unborn and
detached cases are where naive code returns garbage or crashes. With Resolve in
hand, the commit porcelain can find its own starting point, which is what the next
lesson needs.
// symbolic HEAD -> read the branch ref; missing branch -> "" (unborn)func (r *Repo) Resolve(name string) (string, error) {if name == "HEAD" { /* read HEAD */ }// if it starts with "ref: ", recurse on the target ref name// if the ref file is missing, return "" (unborn); else return its id}