Files
DIY_GIT_in_Python/how_to/Change_19.md

4.0 KiB
Raw Blame History

  • 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 weve 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 weve 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 were 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 isnt necessarily the last created commit.

For example, imagine that were 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? Weve 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.