Skip to main content
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Chris Laning
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Chris Laning

Visualizing A Git Merge Without A Rebase

By
Published in Comments (12)

I've been using Git (and GitHub) fairly heavily for about the last 2 years. And, slowly but surely, I'm getting better at it. That said, I still have trouble mentally visualizing some of the operations. And, while "merge" is about as core an operation as you can get, there's something about it that trips me up, mentally. I currently use a rebase-oriented workflow; so, I'm not used to thinking about a merge that doesn't just tack its commits onto the head of the Master branch.

When I'm working on a "feature branch," my merge strategy incorporates a rebase on the Develop branch (the integration branch). Assuming I have a branch called "feature," my workflow would look like something this (not tested):

git commit -am "Commit feature. Werd up!"
git checkout develop
git pull --ff-only
git checkout feature
git rebase develop
git checkout develop
git merge feature

The "git rebase develop" action above ensures that my feature branch changes are added at the end of the develop branch. And, as an added bonus, it allows me to take care of any conflicts during the isolated rebase action, instead of worrying about them in the subsequent merge conflicts.

Adding commits to the end of a branch is an easy thing for me to visualize (in my head). But, not everyone on my team loves to "rebase" before a merge. As such, I need to level-up on my ability to think about merge actions. Specifically, I need to build a better mental model of what the DAG (Directed Acyclic Graph) looks like after a plain merge.

To play with this concept, I created two parallel branches - Alpha and Omega - committed to them in parallel, and then merged them back into master. Then, along the way, I executed "git log" actions so I could see what the DAG was doing.

Ben:git-merge-testing ben$ git init
Ben:git-merge-testing ben$ touch README.md
Ben:git-merge-testing ben$ git add .
Ben:git-merge-testing ben$ git commit -am "(Master) Add initial read me."
Ben:git-merge-testing ben$ git log --pretty=oneline
8a3c828f45ffdeed78ed51ee0e722f755dc9439b (Master) Add initial read me.

Once I had my initial "master" branch in place, I created my two branches and alternated various commits. I made sure to alternate commits so that the final merge couldn't be a "Fast forward" merge by coincidence; the commits in each branch had to overlap.

Ben:git-merge-testing ben$ git checkout -b alpha
Ben:git-merge-testing ben$ touch a-one.htm
Ben:git-merge-testing ben$ git add .
Ben:git-merge-testing ben$ git commit -am "(Alpha) Add first file."
Ben:git-merge-testing ben$ git checkout master
Ben:git-merge-testing ben$ git checkout -b omega
Ben:git-merge-testing ben$ touch o-one.htm
Ben:git-merge-testing ben$ git add .
Ben:git-merge-testing ben$ git commit -am "(Omega) Add first file."
Ben:git-merge-testing ben$ git checkout alpha
Ben:git-merge-testing ben$ touch a-two.htm
Ben:git-merge-testing ben$ git add .
Ben:git-merge-testing ben$ git commit -am "(Alpha) Add second file."
Ben:git-merge-testing ben$ git checkout omega
Ben:git-merge-testing ben$ touch o-two.htm
Ben:git-merge-testing ben$ git add .
Ben:git-merge-testing ben$ git commit -am "(Omega) Add second file."
Ben:git-merge-testing ben$ git checkout master

As you can see, I alternated four commits between the alpha and omega branches (two commits on each branch). Once I had these in place, I then checked out master and merged-in the alpha branch:

Ben:git-merge-testing ben$ git merge --no-ff alpha
Ben:git-merge-testing ben$ git log --pretty=oneline
3fa07f729088af1056190560462309f82c6e2efd Merge branch 'alpha'
657664cad7e31283b60ab2d5abb0c9370ae9bf4a (Alpha) Add second file.
bc99037048fb49bea427b8d81e61cb82e8764c61 (Alpha) Add first file.
8a3c828f45ffdeed78ed51ee0e722f755dc9439b (Master) Add initial read me.

... then I merged-in the omega branch:

Ben:git-merge-testing ben$ git merge --no-ff omega
Ben:git-merge-testing ben$ git log --pretty=oneline
a9828e3eb71bbb13e2ca1aecc53a4fbc752defdb Merge branch 'omega'
3fa07f729088af1056190560462309f82c6e2efd Merge branch 'alpha'
410aeff49304fe28dbafa06fe8c3dba3dc047b0f (Omega) Add second file.
657664cad7e31283b60ab2d5abb0c9370ae9bf4a (Alpha) Add second file.
0f54a9a71f3f1de8ae26672b8e9b5327b6788893 (Omega) Add first file.
bc99037048fb49bea427b8d81e61cb82e8764c61 (Alpha) Add first file.
8a3c828f45ffdeed78ed51ee0e722f755dc9439b (Master) Add initial read me.

The resultant DAG shows us that the omega-merge was "zipped" into the existing commit graph based on the date/time of the actual commits. This is why the Alpha/Omega commits alternate in the log - they alternated in real life.

Honestly, there's something about this that is very hard for me to wrap my head around. I know there is a "holy war" about whether or not to rebase; but, as far as I can see, a rebase-merge workflow results in a significantly simpler mental model. And, as a bonus (in my experience) results in far fewer conflicts.

That said, it's important for me to understand how both merge scenarios play out in the DAG so that I can help my team resolve conflicts. The sad part is, after 2 years, I've probably only scratched the surface of what Git can do.

Reader Comments

1 Comments

As far as I know, a dev team should do a "git rebase master" on a regular basis.

So, not only just before the merge to master, but, let's say, daily.

Working in a feature branch in GIT is sooo much better than in SVN, but you still get to know the changes in master as soon as possible.

This will decrease your amount of conflicts, and when a conflict occurs, the developer of the conflicting code has his changes probably still loaded in his head ;-)

One thing I have sometimes is conflicts with differences in whitespaces while rebasing, but that's another topic.

15,902 Comments

@Rainer,

I'm totally on the same page with you. If I have a feature branch that lasts more than a day, I will definitely execute period rebases on Master so I can solve conflicts as soon as they present themselves. Then, ultimately, the future "merge" is way easier and non-eventful.

4 Comments

Hi Ben,

Have you used Atlassian's sourcetree?

it's free and makes GIT about as simple as version control can get, it also has all the features (and a terminal window if need be)

96 Comments

Git :: I've been using git for about three years now... two actively... I've yet to 'understand' enough of git workflows to become... err 'competent' with git... and I've studied and practiced using it quite a bit...

Regarding the merge w/o rebase thing... I think a lot would depend on you and your teams policies regarding managing commit histories... personally, I don't rebase or merge much... most of my work is just for me... I leave the history pretty much the way it is... I've wrecked a few repos with rebase

Here's a few wise words from Linus...

(http://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html)

;-)

15,902 Comments

@Matt, @Syed,

I'm not too familiar with the Atlassian products. I tried using their issue tracker for a while; but, ended up switching back to Trello for its simplicity. Can you use SourceTree with any git product? Or does it only work with BitBucket?

15,902 Comments

@Edward,

I am definitely a fan of the private "rebase" on your own code. Typically, when I start working on a feature, my first commit will be something very feature oriented, like:

"Add ability for users to perform some action XYZ."

.. then, as I start working on it more, my subsequent commits will vary from the coherent:

"Update data-access layer to handle new column."

... to the non-intuitive:

"Arggg, having trouble getting this working."

... then, once the feature is "complete", but still in my own private branch, I'll rebase it all down to the first, single commit:

git rebase --interactive ABC123

This way, my entire feature is in a single, working commit. And, when I merge it into the main branch, it keeps the history very readable (in my opinion).

96 Comments

@Ben,

I like that school of thought... I've been a bit cautious of what I rebase.... I've had more than a few commits that either caused issues somewhere down the inheritance tree or negatively effected something... somewhere... somehow...

My commit history is essentially one big 'undo' history... waiting for me... ;-)

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel