Go forward in time to January 2009.
The Github blog has an interesting article about using Git to maintain translations for a web page, while accepting contributions from translators easily.
In part 1, we saw how Git makes commits to the repository and how it keeps track of the current branch and other branches. We also saw how remote repositories get registered with your local repository, and how we can fetch changes from them.
In this part, we will see how to visualize what is in the set of fetched changes, and how to integrate those changes into our branches.
Your first commits to a project
Let's say you want to start contributing to the box-of-crayons project, an enterprise-level paint program. When you first start developing for this project, you do something like
git clone git://git.paintastic.com/box-of-crayons.git
(Some shitbag is hoarding that domain name, by the way.)
Your initial repository looks something like this:
(1) is the commit at the tip of your "master" branch; HEAD points to it since this is also your current branch. This is exactly the same as the "master" branch in the origin repository (i.e. the one in git.paintastic.com).
(2) is a commit from which the maintainer of box-of-crayons started a maintenance branch for version 1.0 of the program.
(3) is the tip of the remote's "1.0-branch". You don't have a local ref here yet, but all the commits in that branch exist in your repository. You can examine them with "git log origin/1.0-branch" and "git show <commit-hash>, or more elegantly by pointing and clicking at a visualization tool like "gitk origin/1.0-branch".
As we said before, your "master" branch (or any branch that you create on your own) is your working area. You are free to commit to it. Let's say you start working on a tool to draw straight lines, as any self-respecting enterprise-level paint program will absolutely require a tool to draw straight lines (you may wonder how they ever released 1.0 without such a tool).
So, you do a few commits. The first commit creates a new straight-line-tool class. The second commit registers the tool against the tool manager in the program. The third commit adds a pretty icon for the toolbar. Your repository now looks like this, with your three sexy commits marked in hot pink.
Notice that HEAD didn't change: it still points to the the "master" ref, as this is still your current branch! However, the "master" ref (the movable ref on wheels) now points to your latest commit. Meanwhile, the origin's refs have not moved at all, because you haven't done anything that would cause Git to update them (i.e. you have not fetched any new content from the origin repository).
Fetching, part two
While you are mostly done with your straight line tool, you want to keep up with the official line of development. So you do
git fetch origin
This tells Git, "bring in any new content from the remote repository called origin". Let's look at pictures of your repository before and after fetching:
What just happened? Git noticed that there were two new commits in the "master" branch of the origin repository. Our local name for that branch is remotes/origin/master. Notice that this ref-on-wheels moved to point to the newest of those two commits: (4) is the new tip of the remotes/origin/master branch. Notice that our old (1) is still where it was. (1) is no longer the tip of any branch; it is a plain old commit in the middle, totally un-special like the middle child of a large family.
This is the beauty of Git: when you fetch data from other repositories, Git just hooks the fetched commits to the appropriate places in the graph of commits, but it doesn't fuck with your work (unlike Subversion, where you can't fetch the new code from upstream without risking a conflict-fest in your working copy).
Let's say you use a tool like gitk --all to examine what you just fetched, and you are satisfied with the junk that was brought in. Maybe there are fixes to other parts of the enterprise-level paint program. Let's integrate them into our work! You do this:
git merge origin/master
This tells Git, "merge the origin/master branch into my current branch". Again, let's compare the before-and-after states of merging. Let's assume that there were no conflicts during merging.
Here we see (5), a new commit that was the result of the merge. Note how it has two parents! Whenever you do a merge, Git records where you merged from — this lets it figure things out properly if you do a merge from the same branches in the future (unlike Subversion, which gives you a conflict-fest in that scenario).
As usual when doing a commit, Git advanced your "master" ref to point to the new commit, (5). HEAD still points to "master", so new commits will go to your "master" branch as usual.
Note that the pink commits only exist on your machine. You have not pushed them yet to any remote repository. We will see how to do that later.
Consider a repository where the tip of a remote branch and the tip of a local branch point to the same commit:
Here, (6) is simply the latest commit. Both your "master" ref and the remote's origin/master ref point to exactly the same commit — this is as if you had just cloned a repository and so the branches are identical.
Let's say you do "git fetch origin" and you end up with something like the following (again, you can visualize this with gitk --all):
The "fetch" command brought in three new commits, which are colored in light blue. Note how the remote's origin/master ref moved to point to (7), which is the latest commit that got fetched.
So far so good, right? We haven't made any local changes; we just brought in newer code from the origin repository.
Now what happens if we do the same merge as before — what if we do "git merge origin/master"? Let's see what happens:
Git notices that there is no divergence between (6), the old tip of your master branch, and the string of commits that ends at (7). So Git just moves your "master" ref forward! This is just what you expect: if you haven't made any changes and you just fetch in newer code, merging that code should produce no conflicts at all — you just get the newer code and that's it.
Git calls this situation a fast-forward. Whenever Git tells you that it was able to fast-forward a merge, or that a branch is a certain number of commits behind a fast-forwardable one, this is a good thing — it means that merging right there would be a trivial operation.
In the next part, we'll see what to do about merge conflicts — that is, when merges are not trivial operations.
Go backward in time to November 2008.Federico Mena-Quintero <email@example.com> Tue 2008/Dec/09 18:24:13 CST