|
|
|
@ -0,0 +1,90 @@
|
|
|
|
|
- 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.
|