Compare commits
2 Commits
d00a7817ab
...
b802e1eb9d
| Author | SHA1 | Date | |
|---|---|---|---|
| b802e1eb9d | |||
| 817f38f49c |
90
how_to/Change_19.md
Normal file
90
how_to/Change_19.md
Normal file
@@ -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.
|
||||||
@@ -97,6 +97,12 @@ def commit(message):
|
|||||||
return oid
|
return oid
|
||||||
|
|
||||||
|
|
||||||
|
def checkout(oid):
|
||||||
|
commit = get_commit(oid)
|
||||||
|
read_tree(commit.tree)
|
||||||
|
data.set_HEAD(oid)
|
||||||
|
|
||||||
|
|
||||||
Commit = namedtuple("Commit", ["tree", "parent", "message"])
|
Commit = namedtuple("Commit", ["tree", "parent", "message"])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ def parse_args():
|
|||||||
log_parser.set_defaults(func=log)
|
log_parser.set_defaults(func=log)
|
||||||
log_parser.add_argument("oid", nargs="?")
|
log_parser.add_argument("oid", nargs="?")
|
||||||
|
|
||||||
|
checkout_parser = commands.add_parser("checkout")
|
||||||
|
checkout_parser.set_defaults(func=checkout)
|
||||||
|
checkout_parser.add_argument("oid")
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
@@ -85,3 +89,7 @@ def log(args):
|
|||||||
print("")
|
print("")
|
||||||
|
|
||||||
oid = commit.parent
|
oid = commit.parent
|
||||||
|
|
||||||
|
|
||||||
|
def checkout(args):
|
||||||
|
base.checkout(args.oid)
|
||||||
|
|||||||
Reference in New Issue
Block a user