Note: I led the project to convert a team's Source Control Management tool and processes from Microsoft Visual SourceSafe to Git. This is an edited version of a How-To document I wrote and distributed in response to some questions about Merge vs Rebase in Eclipse.
As you know, I’ve been recommending using local Git branches to separate your projects. By that, I mean create a branch from the Dev branch, make your changes and commit them as often as necessary to your local branch, switch branches when you need to switch projects, etc.
As you know, I’ve been recommending using local Git branches to separate your projects. By that, I mean create a branch from the Dev branch, make your changes and commit them as often as necessary to your local branch, switch branches when you need to switch projects, etc.
One side-effect of using local branches is that when you
bring your work into the main Dev branch, it leaves a new commit, a Merge
commit. This means that, even though we do not have feature branches on the
server, we can still wind up with a history that looks like this on the server:
We wind up with the clutter of parallel lines showing the
life spans of our local branches, and extra commits (nine in the above
screen-shot, marked by the green arrows icon).
We are a small enough team that we can probably manage,
although here is an extreme-case history from another team: http://agentdero.cachefly.net/unethicalblogger.com/images/branch_madness.jpeg
Or maybe we find it unnecessarily long and cluttered.
Maybe instead we prefer the history to tell the bigger
story, and not dwell on the tiny details. Merge-commits are low-level details
that can get in the way of understanding the bigger picture.
There is a way to use Git to clean up the commits and the history. It’s called “Rebase.”
You likely spent a little time learning about Rebase during
your 1-on-1 Git Orientation sessions this spring. But it may have been lost in
the blur of meeting Git for the first time.
Rebase offers us 2 benefits:
1.
Rebase allows us to clean up a set of local commits
before pushing them to the shared, central repository. For this, the Rebase
Interactive tool is helpful in rewording, squashing etc. several commits
into a smaller number of more logical chunks;
2. Rebase lets us merge branches more cleanly, with
fewer Merge-commits left over.
#2 is what we need in order to bring our changes from one
branch onto another branch, in a way that avoids the parallel lines and merge
commits in the above image.
Warning: while the steps and concepts are reasonably
straight-forward, Rebase is an advanced-enough feature that before using this
on production code, you should be comfortable with the Git concepts of Branches,
Commits, Fetches, Merges, Conflicts, etc. I highly recommend you take a couple
hours to set up a practice repository and play around a bit until you are
comfortable.
And in particular, follow this Think-Local Rule:
Do not rebase commits that exist outside your local repository
Do not rebase commits that exist outside your local repository
How to Rebase Branches in Eclipse:
Imagine that you have been working for a few days in a local Git branch, maybe for a new feature or a bug fix. For example: Project93979
Imagine that you have been working for a few days in a local Git branch, maybe for a new feature or a bug fix. For example: Project93979
Now you are done the work. All your changes, tests and new classes
have been committed to your local branch in your local repository. You are now ready
to merge it into your main local Dev branch so that you can push it up to the
server.
In order to use Rebase instead of Merge in Eclipse, these
are the steps:
1.
Check out your local Dev branch.
2.
Do a Pull from the server - your local Dev
branch should now have the latest, up-to-date copy of all the source files.
3.
Check out the Project93979 branch again.
4.
Rebase the commits on the Project93979 branch
onto the Head commit of the Dev branch.
What does that mean? Remember that Git is
basically a directed graph, where each commit contains the set of changes
between it and the previous commit.
When you created the Project93979 branch
from your Dev branch, the paths of commits in each branch diverged at that
point, their common-ancestor commit.
Rebasing is asking Git to take all the
commits in one branch, and apply them starting from the most recent commit in
another branch, rather than from the common ancestor commit.
In the image above, the Project93979 branch
was created at the commit labelled “A” - it is the common ancestor. Since then,
we have made commits “Y” and “Z” on the local Project93979 branch, and a few
other commits have been made on the Dev branch, by us or others.
If you Merge the Project93979 branch into the
Dev branch, it creates a new Merge commit on the Dev branch. Conceptually, the
commit graph will look like the image below. The Dev branch’s head will point
to the new Merge commit “M” while the Project93979 branch head will continue to
point to commit “Z”
The goal of Rebasing is to bring the two branches
together by replaying the changes of the commit(s) made to one branch onto the
most recent commit of the other. The result of a Rebase will produce a commit
graph like the image below:
Commits “yr” and “zr” are the result of Git
replaying commits “Y” and “Z” onto the Dev branch’s commit “E” instead of where
the changes originally stemmed from, the common ancestor commit “A”.
How to
do such a Rebase in Eclipse:
1.
Open your Git Repositories view and expand the
Local Branches. Make sure Project93979 has the check-mark as the one checked
out.
2.
Next, Right-click on the local Dev branch and
select Rebase On.
1.
If there are no conflicts, go to Step 9.
2.
If there ARE conflicts, you have the usual
manual-merge conflict-resolution work to do. This is exactly the same work you
would have to do if you did a Merge instead of a Rebase, with one catch: If you
changed a file in a way that makes a conflict several times over several
commits in the branch, you need to resolve the conflict for each of those commits
(in a Merge, you resolve it just once).
Eclipse will notify you of conflicts with a
dialog like this (in Eclipse Mars):
Click OK to start the Merge Tool. Use the Merge Tool
to navigate the conflicts and copy or change the source as needed to resolve
the conflicts. It may look something like this, which has an import and a
superclass that need to be replaced:
Once the changes are completed, save the merged files
and switch to your Git Staging view. The conflicting file may still have the
red Conflict icon decorator in the Unstaged Changes box, but since we have
resolved them and saved, right-click and add them to the Index. When we do
this, the Conflict decorator is replaced with the Changes snowflake and the Commit
button is enabled.
In the above image, the Continue button is
also enabled at this point. That is because Rebase is replaying the series of
commits we made on this branch onto the new starting-point commit, as shown in
the graph diagrams above. And I remember that I made several commits on the
branch. “Continue” lets us move forward, either to the rest of the commits, or
to the rest of the Rebase process.
You can click Abort to back out of the
Rebase process at this point, or Skip to move to the next commit in the branch,
or Commit to make a new commit of the resolved conflicts, or Continue to proceed
with the Rebase process.
Once there are no conflicts, you have
successfully updated your branch Project93979 to look how it would have had you
branched from the very latest of the Dev branch and then made all your changes.
This is the goal of Rebase and, when there are no conflicts, Git handles this
very nicely.
If
you want to keep working on the branch changes, you have now updated it to the
latest from the central repository.
However, if you want to bring your changes into the Dev branch, in preparation
for pushing them to the remote server, there are a couple more steps:
1.
Now, Rebase the Dev branch onto your Project93979
branch. Check out the Dev branch again and Right-click on the Project93979
branch, then select Rebase On. This should be trivial, since you have already
resolved all of the conflicts in Step 5. It should just be what Git calls a
Fast-Forward.
2.
Push your changes upstream. Whatever commits
were in the Project93979 branch should now appear in the history of the Dev
branch, but with no Merge-commit and no clutter of parallel lines showing
branches that never really existed on the remote repository. The image below is
the Eclipse Git History view, and you can see that the Project93979 branch has
been brought together with the dev branch cleanly.
3.
If you are done with your local Project93979
branch, you can delete it now. or you can keep the branch, and continue to work
with it. The branches will diverge at this new common-ancestor point.
Further reading: http://unethicalblogger.com/2010/04/02/a-rebase-based-workflow.html