Skip to main content

Git Rebase in Eclipse

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.

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

The examples above show all the gory details of how the code base evolved. Maybe that’s what we want or need.

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

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

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.


Popular posts from this blog

How to do Git Rebase in Eclipse

This is an abbreviated version of a fuller post about Git Rebase in Eclipse. See the longer one here : One side-effect of merging Git branches is that it leaves a Merge commit. This can create a history view something like: The clutter of parallel lines shows the life spans of those local branches, and extra commits (nine in the above screen-shot, marked by the green arrows icon). Check out this extreme-case history:  http://agentdero.cachefly.net/unethicalblogger.com/images/branch_madness.jpeg Merge Commits show all the gory details of how the code base evolved. For some teams, that’s what they want or need, all the time. Others may find it unnecessarily long and cluttered. They prefer the history to tell the bigger story, and not dwell on tiny details like every trivial Merge-commit. Git Rebase offers us 2 benefits over Git Merge: First, Rebase allows us to clean up a set of local commits before pushing them to the shared, central repository. For this

Java 8: Rewrite For-loops using Stream API

Java 8 Tip: Anytime you write a Java For-loop, ask yourself if you can rewrite it with the Streams API. Now that I have moved to Java 8 in my work and home development, whenever I want to use a For-loop, I write it and then see if I can rewrite it using the Stream API. For example: I have an object called myThing, some Collection-like data structure which contains an arbitrary number of Fields. Something has happened, and I want to set all of the fields to some common state, in my case "Hidden"

Git Reset in Eclipse

Using Git and the Eclipse IDE, you have a series of commits in your branch history, but need to back up to an earlier version. The Git Reset feature is a powerful tool with just a whiff of danger, and is accessible with just a couple clicks in Eclipse. In Eclipse, switch to the History view. In my example it shows a series of 3 changes, 3 separate committed versions of the Person file. After commit 6d5ef3e, the HEAD (shown), Index, and Working Directory all have the same version, Person 3.0.

Code Coverage in C#.NET Unit Tests - Setting up OpenCover

The purpose of this post is to be a brain-dump for how we set up and used OpenCover and ReportGenerator command-line tools for code coverage analysis and reporting in our projects. The documentation made some assumptions that took some digging to fully understand, so to save my (and maybe others') time and effort in the future, here are my notes. Our project, which I will call CEP for short, includes a handful of sub-projects within the same solution. They are a mix of Web APIs, ASP MVC applications and Class libraries. For Unit Tests, we chose to write them using the MSTest framework, along with the Moq mocking framework. As the various sub-projects evolved, we needed to know more about the coverage of our automated tests. What classes, methods and instructions had tests exercising them, and what ones did not? Code Coverage tools are conveniently built-in for Visual Studio 2017 Enterprise Edition, but not for our Professional Edition installations. Much less for any Commun

Scala Collections: A Group of groupBy() Examples

Scala provides a rich Collections API. Let's look at the useful groupBy() function. What does groupBy() do? It takes a collection, assesses each item in that collection against a discriminator function, and returns a Map data structure. Each key in the returned map is a distinct result of the discriminator function, and the key's corresponding value is another collection which contains all elements of the original one that evaluate the same way against the discriminator function. So, for example, here is a collection of Strings: val sports = Seq ("baseball", "ice hockey", "football", "basketball", "110m hurdles", "field hockey") Running it through the Scala interpreter produces this output showing our value's definition: sports: Seq[String] = List(baseball, ice hockey, football, basketball, 110m hurdles, field hockey) We can group those sports names by, say, their first letter. To do so, we need a disc