git mental model

Finally I managed to get a mental model of git that allows me to do everything without needing to google around. And this is a quick memory dump.

There are other git cheatsheets, but this is mine.

History is a tree of single-linked nodes, each pointing to its parent and containing its parents’ SHA1 - so:
  • changing a node (which changes its SHA1) needs changing all the children. This shapes normal git usage. Anyway, it CAN be done, and usually git itself will do it for you (no extra tools needed). Incidentally: Git tends to consider history changeable, Mercury doesn't.
  • a parent does not know of its children. So children are inaccessible by themselves*; and so they will be eventually garbage-collected if there is not something pointing to them: branch heads, tags, HEAD. (though reflog is a safety net)
A branch head is a pointer to the node where new children will get added (committed), and is what is usually meant when talking colloquially about “branches”. But strictly speaking, a branch is the whole history (chain of nodes) from a branch head to the tree root. Can be seen as a singly-linked list.

HEAD is kind-of a thumb for thumbing around in the book of history: a pointer pointing to the implicit working place in the history. Normally, HEAD points to a branch head (which points to a node in the history tree), but HEAD can point directly to a node – and then it is a DETACHED HEAD.
A DETACHED HEAD is "alarming" because by definition it means that the history node it points to is not pointed-to by any branch head; so, if we move the DETACHED HEAD (our "thumb") somewhere else, there might be nothing pointing to that node. It gets lost, and eventually garbage-collected (though there are safety nets).
Want to go back later to that page in the history book? Either keep your thumb there, or put some kind of bookmark - a branch head (there are other options).

Index, AKA staging, AKA cache, is a snapshot in time of the working directory; so it implements all history from the beginning to a given commit.
Committing will:
  1. take the difference between stage and HEAD
  2. create a new history node with that diff
  3. point it to the current HEAD
  4. and point to it with HEAD.
In other words:
  • In the simpler case of a DETACHED HEAD, HEAD can be seen as the pointer to the tail of a linked list (which is the chain of nodes from the history root to the node pointed to by HEAD); and making a commit is adding a new node to that linked list.
  • In the more normal case where HEAD is NOT detached, HEAD points to a branch head, which points to the node which is the tail of the linked list (and that linked list is what we call "branch"). When committing, the node is added to the linked list, and the branch head is moved together with HEAD. And so at the end the branch has grown, and is still ready to keep growing further.

git checkout <commit>
moves HEAD (not any pointed-to branch head) to that commit; updates index and workdir
  • if <commit> is a branch, then HEAD is pointed to the branch
  • with -b you create a new branch, to which HEAD points and where new commits will go

EXAMPLE: git checkout HEAD~1 will detach the HEAD and take it to the parent of the last commit. The workdir will look like it looked when that commit was done. Even if HEAD pointed to a branch head when the command was entered, the operation is the same: that branch head stays where it was.

git reset <commit>
move branch pointed to by HEAD to that commit, set index according to that point in history (this is what --mixed does; it’s the default)
  • --hard also makes the working directory the same as the index
  • --soft only moves the branch pointed to by HEAD, doesn’t touch index nor workdir
EXAMPLE: git reset HEAD~1 will move HEAD and its pointed-to branch to the parent of the currently pointed-to commit, and so potentially make the last commit be unreachable; “potentially” because if there is any branch head there (any branch head not pointed-to by HEAD), then it will continue where it was, and so the node will continue to be reachable. Also, this command doesn’t change the state of the working dir, so git status will report that the changes that had been committed in the last commit are not presently committed. This is the first step to “amend the last commit”.

git reflog
log of references held by HEAD
EXAMPLE: to undo the last HEAD movement, do git reset HEAD@{1}

git branch
plays with branches, not with HEAD

* Well, nodes are always accessible by their SHA1s, but that won't save them from the Grim Garbage Collector.

No comments

Post a Comment