- 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.
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-ffoption 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.
# 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).
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
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 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!