Is there a fast way to rebase a long history of commits to master branch?
I've been working for the past two weeks on a feature, and I created a separate branch for it. The problem is that the task took me a long time, and I didn't rebase my branch to master periodically. I ended up with 100+ commits and now when I'm trying to rebase the branch to master using git rebase master
, it is taking forever, as I need to check the conflict for each each commit, modify it, git add
and then git rebase --continue
.
Is there an easier way to do that?
Solution 1:
This depends in part how you've done your 100+ commits and what's been happening in the meantime. Four strategies might be useful:
Reduce the number of commits to rebase
If you have a beautiful clear stream of 100 commits that never modify the same code twice, I'm sorry, but you are stuck.
However, from 100 commits in two weeks, I'm guessing your commit history does not look like that. Perhaps you've modified function X 5 times within the commit history, and each one of those commits is going to cause a conflict. If only you had one single commit that modified function X, you'd have 20% of the work to do.
So the answer here is to tidy up your commit history before you rebase onto master. This is another use for 'git rebase'. Rebase it onto its existing base, but use git rebase -i
to interactively squash together and reorder the commits. Make sure you have the minimum number of commits and that each of those does something independent. You can take the opportunity to change your commit logs too. Key backup branches (git branch
) and git diff
to those after each commit to check you've not changed anything.
In particular, if you have internal merge commits, flattening these is helpful.
Inevitably when I do this I find stuff I did wrong, or could have done better, even if it's 'only' code formatting or commit messages. You can take the opportunity to fix these. I'm guessing you should be down to 10 or 20 commits when this is done - far more manageable.
Rebase in stages
If you've already done that, you are probably going to have to bite the bullet. However, if a colleague has been refactoring whilst you've been working, sometimes what you are best doing is as follows. Imagine the tree looks a bit like this:
A --- B --- C --- D --- E --- F -- .... -- X --- Y --- Z (master)
\
\----- 1 --- 2 --- 3 --- 4 .... 97 --- 98 --- 99 --- 100 (you)
What you want to do ultimately is to rebase onto Z. However, this gives you loads of conflicts. Let's suppose commits D and E are two megacommits from a colleague refactoring something your code is dealing with. Sometimes, it's easier to:
- Rebase onto C (this shouldn't be much work)
- Take a deep breath and rebase onto E
- Rebase onto master (this shouldn't be much work)
Avoiding complexity on the 'master' side
Sometimes things have got really complicated on the tree you are trying to merge into. A classic case is that (using the above diagram), commit G reverts commit C, then commit H redoes (or almost redoes) it, and in the mean time there are merges (particularly of things you've might have merged in too). Thanks colleague, that's made rebasing really simple. Other things that cause difficulties are complex merge commits and nasty renames. Each commit tends to give you the same conflicts again and again for no apparent reason. git rebase
Aarrggh.
One technique here is to flatten a copy of the master tree, i.e. branch master (locally), then rebase it squashing the whole thing into a single commit from A to Z. You can flatten your stuff a little first too (see above). Then rebase onto the flattened master. Now you've got the 'right' set of commits, you can easily rebase that onto master (well, onto 'Z' as master may change) because the code's exactly the same.
If all else fails
Sometimes git rebase
seems to go into a fugue state of conflicts, and it's time to break out git format-patch
and git am
to reapply it in whole or in bits. This is really useful if someone has renamed a file (as you can fix the filename in the patch in your editor) or renamed a common class/variable/whatever (as you can find/replace in the patch).
Learn from your mistakes
Next time, rebase onto master more frequently. It's less painful if you do it as you go along.
Also, if you have two people working on the same area of code and it produces a pile of conflicts, perhaps your code's not well abstracted enough, or perhaps you are working on top of each other and could divide up your work better?
Solution 2:
rebase via merge
Let me describe approach I have found.
I had the same rebase pain, without "just merge" option. And I knew that we have rebase flag -X theirs, which automatically resolves all conflicts with our changes, but that works mechanically and we are losing external changes.
But what if we could add one additional commit to restore project state from a temporary merge? And I found and easy way to do that.
First, checkout temp branch and start merge
git checkout -b temp
git merge origin/master
You will have to resolve conflicts, but only once and only real ones. Then stage all files and finish merge.
git commit -m "Merge branch 'origin/master' into 'temp'"
Then return to your branch (let it be alpha) and start rebase automatically resolving any conflicts.
git checkout alpha
git rebase origin/master -X theirs
Branch has been rebased, but project is probably in invalid state. That's OK, we have one final step. We just need to restore project state, so it should be like we have on branch 'temp'. Technically we just need to copy it's tree (folder state) via low-level command git commit-tree. Plus merging into current branch just created commit.
git merge --ff $(git commit-tree temp^{tree} -m "Fix after rebase" -p HEAD)
And delete temporary branch
git branch -D temp
That's all. We did a hard rebase via hidden merge.
Also I developed a script, so it can be done in dialog manner, you can find it here.