4.0 KiB
- checkout: Read tree and move HEAD
When given a commit OID, ugit checkout will “checkout”
that commit, meaning that it will populate the working directory with
the content of the commit and move HEAD to point to it.
This is a small but important change and it greatly expands the power of ugit in two ways.
First, it allows us to travel conveniently in history. If we’ve made a handful of commits and we would like to revisit a previous commit, we can now “checkout” that commit to the working directory, play with it (compile, run tests, read code, whatever we want) and checkout the latest commit again to resume working where we’ve left.
You might be wondering why checkout is needed when we
could just use read-tree, and the answer is that moving
HEAD in addition to reading the tree allows us to record which commit is
checked out right now. If we would only use read-tree and
later forget which commit we are looking at, we will see a bunch of
files in the working directory and have no idea where they came from. On
the other hand, if we use checkout, the commit will be
recorded in HEAD and we can always know what we’re looking at (by
running ugit log for example and seeing the first
entry).
The second way by which checkout expands the power of
ugit is by allowing multiple branches of history. Let me explain: So far
we have set HEAD to point to the latest commit that was created. It
means that all our commits were linear, each new commit was added on top
of the previous. The checkout command now allows us to move
HEAD to any commit we wish. Then, new commits will be created on top of
the current HEAD commit, which isn’t necessarily the last created
commit.
For example, imagine that we’re working on some code. So far, we have created a few commits, represented by a graph:
o-----o-----o-----o
^ ^
first commit HEAD
Then we wanted to code a new feature. We created a few commits while working on the feature (new commits represented by @):
o-----o-----o-----o-----@-----@-----@
^ ^
first commit HEAD
Now we have an alternative idea for implementing that feature. We
would like to go back in time and try a different implementation,
without throwing away the current implementation. We can remember the
current HEAD and run ugit checkout to go back in time, by
providing the OID of the commit before the new feature was implemented
(that OID can be discovered with ugit log).
o-----o-----o-----o-----@-----@-----@
^ ^
first commit HEAD
The working directory will effectively go back in time. We can start working on an alternative implementation and create new commit. The new commits will be on top of HEAD and look like this (represented by ): ``` o-----o-----o-----o-----@-----@-----@ ^ \ first commit ----—–$ ^ HEAD
See how the history now contains two "branches". We can actually switch back and
forth between them and work on them in parallel. Finally, we can checkout the
preferred implementation and work from it on future code. Assuming that we liked
the second branch, we'll just keep working from it, and future commits will look
like this:
o—–o—–o—–o—–@—–@—–@ ^
first commit —------—–o—–o—–o—–o—–o ^
HEAD ```
Pretty useful, right? We’ve just introduced a simple form of
branching history. Note that something pretty cool happened here: The
implementation of checkout is very simple (we just call
read_tree and update HEAD) but the implications of checkout
are quite big - we can suddenly have a branching workflow which might
look complicated but it is actually a direct consequence of what we
implemented in previous changes. This is why I believe learning Git
internals from the bottom up is useful - we can see how simple concepts
compose into complicated functionality.