After reading a lot about git merge and git rebase, my takeaway is this:
Use git rebase when:

  • Working alone on a feature branch that isn’t and won’t be shared with anyone. Rebasing will keep your history cleaner.
  • You’re done working on a feature branch and about to integrate it into the main branch. Use rebase to sanitize your history and squash/rewrite history as needed before the ‘big merge’ onto the main branch.

Use git merge when:

  • Working alone on a feature branch and others may be work off of your branch. This way history isn’t rewritten accidentally which would affect break how others merge down the line.
  • You completed a feature branch and want to integrate into the main branch. Make sure to use the --no-ff option to opt-out of fast-forward commits.

So I thought that maybe rebasing is what I needed to do to. If you didn’t catch this in my previous post, the current state of my local git repo is this:

 J - K - [lots of commits] - A - B - C - T  [master]
  \                         /
   - A - B - C ------------            [Alice's master]
              \
               - G - H - I             [my feature branch]

My feature branch basically merged in changes from Alice’s branch. At the time Alice’s branch hadn’t yet been integrated into the master branch, but now it it part of it. So I thought that I would need to rebase my branch onto the updated master branch, so that my changes could easily be fast-forwarded on top of the Alice’s commits, which were already in the main branch.

Using this as a guide, I did:

git checkout bootstrap-v3-overview
git rebase --onto master alice-branch bootstrap-v3-overview
# I was confronted with a lot of merge conflicts
git mergetool
git rebase --continue

I was confronted with a lot of merge conflicts. I used meld to step through each of the conflicts. Once I fixed everything, I continued the rebase process:

git mergetool
git rebase --continue

Note that it may seem like you have more merge conflicts when doing git rebase. In my case, this happened because rebase was trying to apply my commits one-by-one. If it couldn’t apply my commit (because of merge conflicts), it stopped and waited for me to resolve it before continuing. That means that:
1. The REMOTE/LOCAL versions you see when using mergetool isn’t the latest version of your files. That’s because it’s at the time of that particular commit. So essentially you’re resolving conflicts as if you were back in time, after having just made that particular commit (but before making the rest of the latter changes).
2. You might end up making edits to the same file multiple times. But you won’t be making the same edits to the same file. It’s simply because of the context. It just means that you touched that same file across multiple commits. So each time there’s a merge conflict after trying to apply one of those commits, then you have to go back and edit that file.
But don’t be intimated by the potential increase in merge conflicts. Usually merge conflicts resulting from git rebase are smaller than those resulting from git merge. This is because (usually) less has changed between each of the commits that git rebase is trying to apply.

After a few rounds of resolving merge conflicts, git rebase left my repo looking like this:

 J - K - [lots of commits] - A - B - C - T                         [master]
                                          \
                                           - A - B - C - G - H - I [my feature branch]

The good thing is that everything is linear now (which means only fast-forward commits are needed). However, there are now duplicates in the my tree. In particular, Alice’s commits got duplicated and brought in as well.

Now I could have used git rebase again to perhaps rework the commit history and squash/remove the duplicate commits. However, after thinking a little more about my problem, I decided that perhaps git rebase wasn’t the best solution and instead git cherry-pick or git format-patch was better suited to my situation. My thinking is that, what I’m basically doing is copying over part of a branch (my feature branch) onto another branch (the master branch). I only want a part of it because I want to ditch the commits where I was merging in stuff from Alice’s master branch. So it’s kind of like a “partial merge”, if you will, of my feature branch into the master branch.

Using this and this as helpful references, I decided to go with using git format-patch:

# first I wanted to reset everything
git checkout master
git reset HEAD --hard
# create a new working branch, which we'll apply the patch to
git checkout -b redesign-listoverview
# pull commits from my feature branch to create patch
git checkout bootstrap-v3-overview
# id1 & id2 indicate a range of commits. note that the first id1 is non-inclusive
# that means the patch starts after that commit. the last id2 is inclusive
git format-patch -k --stdout revision-sha1-id1..revision-sha1-id2 > mypatch.patch
# apply patch to new working branch
git checkout redesign-listoverview
git am -k -3 mypatch.patch
# still have to do deal with merge conflicts
git mergetool
git am --resolved

This time you’ll notice I created a working branch to apply my patch to. This is completely optional. I could have just as easily applied my patch to my master branch as well. I was assuming that having a working branch might make my pull request in GitHub easier to deal with later (but I’m not sure if that statement is actually true or not).

Also, git am is similar to git rebase in the sense that both try to apply commits one-by-one, which mean that you may have to deal with several rounds of merge conflicts, just like in git rebase.

But what I do know is that after applying the git format-patch and git am, my local git repo now looks like this:

 J - K - [lots of commits] - A - B - C - T             [master]
                                          \
                                           - G - H - I [my working branch]

which is exactly what I wanted!

As the very last step, I noticed that I wanted to cleanup my commits by combining the last two commits into one. To do that, I just used git rebase:

git checkout redesign-listoverview
# R1 is the revision before the revision you want to edit
git rebase -i revision-sha1-id

Then in the editor that appears, I used the squash command to combine the last two commits into one.

Whew, that was a long process for me! But in mucking around with git, I learned a lot and feel a little bit more comfortable dealing with git merges, rebases, and branches.

Of course, the downside is that, in my initial haste, I already submitted a pull request (that’s based on an outdated head). Now I’ve created a new working branch (redesign-listoverview), which is based on an updated head, but it’s not related to the old branch (bootstrap-v3-overview), which means that GitHub won’t automatically update the pull request with these changes I just made. I suppose I’ll just close that pull request and create a new one. Hopefully this time my pull request will be done correctly!

Leave a Comment

Your email address will not be published. Required fields are marked *