[kwlug-disc] Free Facts Mini Howto: git's range specifiers
Chris Frey
cdfrey at foursquare.net
Thu Sep 24 00:19:30 EDT 2009
Git has the ability to specify ranges of commits, with flexible variations.
But with that flexibility comes complexity. Have you ever wondered
what the difference was between "commit1..commit2" and "commit1...commit2"?
If so, then this mini howto is for you.
(If you want to follow along, use the sample setup commands at the end of
this document)
1. What is a branch?
--------------------
It is helpful to remember that git thinks of a branch as any name or
tag that refers to a SHA1 commit ID. The branch represented by that
commit includes that commit itself along with all the ancestors that
make it up.
For example:
git log master
will show you the master commit, with all the parent commits that belong
to it. If you say:
git log master desktop
it will list all the ancestor commits of both master and desktop.
2. Commit walking
-----------------
The command git-rev-list takes a list of commits and prints the SHA1
commit IDs of all the ancestors for those commits. This is a low
level git command and is the basis for specifying commit ranges.
For example, on a repository I have on my hard disk, I have the following
branch structure:
/---o---o master
/
o----o (base)
\
\---o---o desktop
The following command:
git rev-list master | wc -l
shows 3 commits. If I include the desktop branch, which has 2 unrelated
commits in it, and does not have the 2 commits from master:
git rev-list master desktop | wc -l
it shows 5 commits.
This shows that specifying multiple branch names is inclusive. All commits
from all branches are included in the resulting list of ancestors.
3. Limiting the list
--------------------
If I want to see all commits that are in master but not in desktop, I
can invert one of the branches:
git rev-list master ^desktop | wc -l
This shows 2. The two commits on the master branch. I can verify
this with:
git log master ^desktop
which shows only the patches on the master branch.
I can reverse this as well, showing only the 2 commits on the desktop
branch:
git log ^master desktop
The shorthand for this is the 2 dot ellipsis. The following are
identical:
git log ^master desktop
git log master..desktop
Again, reversing, the following are identical:
git log master ^desktop
git log desktop..master
4. Finding the branch point
---------------------------
Git does not store branch divergence points. This can be calculated on
the fly by looking at the heads of two branches and finding the first
common commit in the list of ancestors.
This job is done by git-merge-base. If we used it on the above
branch structure, we would get a SHA1 commit ID of the point marked (base)
above.
git merge-base master desktop
This returns c79f6f286e720b39976531b7a5b713b87308b576 in my repo.
It will be different in yours, since you have different author information.
5. Listing all changes
----------------------
Using what we know, we can now list all the changes on both branches
that are not in the main repo:
git log master desktop ^c79f6f
This shows 4 commits, from both branches.
The shortcut to this, is the triple ellipsis:
git log master...desktop
Basically, this means show all the commits from both branches that happened
since they diverged.
6. Diff: turn the world upside down
-----------------------------------
This is all fine and good, but wait! The git-diff command turns this
inside out.
Git-diff takes two branches and shows a unified diff patch between
the _endpoints_ that they represent. The usual format of the command
is as follows:
git diff master desktop
This will create a patch which, if applied to master, will turn it into
desktop. i.e. it removes the 2 changes done on master and adds the
2 changes done on desktop.
The reverse will do as expected:
git diff desktop master
This is the same as having two trees checked out, and running a manual
diff -ru on them from the command line.
The double ellipsis is used in git-diff as well, but has no special meaning.
It is an alias for the above usage. The following are identical:
git diff master desktop
git diff master..desktop
The difference in git-diff is that the triple ellipsis uses the same
logic as git-log does when finding the merge-base, but the result
is the difference between the base and _one_ of the branches.
For example:
git diff master...desktop
This will find the branch point of master and desktop, and then generate
a diff between that base commit and the commit of desktop.
In other words, the diff will contain only the 2 changes on the desktop
branch, and is effectively the same as:
git diff c79f6f desktop
or
git diff $(git merge-base master desktop) desktop
Reversing the options will do the same for master. The following
commands are identical:
git diff desktop...master
git diff c79f6f master
Now, the diff will contain only the 2 changes on the master branch.
The parsing logic of the triple ellipsis range is the same, but the result
is the opposite. With git-log, all 4 commits are shown, with git-diff,
only 2 commits from one side of the branch are shown.
7. Play
-------
Pasting the following commands into a shell will create a sample branch
with which you can experiment with these range specifiers.
mkdir play
cd play
git init
echo data > file.txt
git add file.txt
git commit -m "Initial commit"
echo data > README
git add README
git commit -m "Added README"
echo data > license
git add license
git commit -m "Added license file"
git checkout -b desktop master^^
echo data > main.c
git add main.c
git commit -m "New source code"
echo data >> main.c
git commit -a -m "Bug fix"
git checkout master
Enjoy,
- Chris
<cdfrey at foursquare.net>
2009/09/23
More information about the kwlug-disc
mailing list