Recently I was facing a difficult situation: me and my team we’re working on a feature (using Feature Branch Model) and I wanted to rebase my code to the changes on another branch. Nothing special so far. But the problem was that there were several conflicts because we’ve changed code at similar lines. After finishing the rebase and making a push force to the upstream, I’ve realised that I’ve made a small mistake when resolving the conflicts. Well, the problem was that I’ve selected the wrong changes which should be applied. But reverting by just checking out my old changes and doing the rebase again was obviously no option. Therefore I want to share my experience with you in this blog post and give you a process how to revert such rebasing errors.
Before Rebasing
Before rebasing any changes, the situation looked like this. At some point I’ve checked out a new branch called TEST01. Let’s say at this point both branches had a text file with the following content:
Text 123 Changes 1
Now I’ve made some changes (blue circles). After that, the text file might have looked like this:
Text 123 Changes 1 Some more changes. But nothing more. TEST01: - Change 1
In the meantime, someone else made also changes on the original/master branch (green circles) and pushed them to the upstream. The file might look like this:
Text 123 Internal Changes 1
As you might guess correctly, the third line produced a conflict while rebasing, because on both branches this line has changed. On the original/master it contained the additional word “Internal” and on the branch TEST01 some more lines have been added.
After Rebasing
Let’s try to go through the process of rebasing the branch TEST01 onto the original/master branch. In order to do this, pull the latest changes, checkout branch TEST01 and try to rebase: git rebase master. Git will output that it has detected a conflict and you have to resolve it. It might look lik this:
Text 123 <<<<<<< 58751f995455c69cce34166888618e685fa05ae7 Internal Changes 1 ======= Changes 1 Some more changes. But nothing more. TEST01: - Change 1 >>>>>>> TEST01 added first change to text.txt
If you resolve this conflict, it might introduce a rebasing error. For example: if you decide to not include the word “Internal”, it might not be important in this example. But if you consider having a huge software project where multiple lines are in conflict and e.g. you missed to include one important if-condition, it might screw your whole project. For simplicity, let’s just assume I forgot to include “Internal” when resolving the conflict.
After resolving the conflict, the branch should have my changes on top like in the following figure:
This might be fine for now, but what if you encounter any issues afterwards? For example 50% of your tests fail. Then you have to find the needle in the haystack.
Revert Rebasing Errors
It might be easier to review your rebase and find something you’ve missed. This is where the problem can get tricky. A good starting point might be to search at the conflicting lines. You could just look at the difference of your current code and the code from the commit which was in conflict with your changes. Well… nice try! If you resolve conflicts, Git will rewrite the commit history, because commits are immutable. This means you can’t just go back with git reset
, because your history has been overwritten. So what else can you do?
First, remember the conflicts you’ve encountered whil rebasing. Do you have them? Fine, keep them in your mind, because now you have to iterate through them!
Second, search the log history with git reflog. From the documentation:
Reference logs, or “reflogs”, record when the tips of branches and other references were updated in the local repository. Reflogs are useful in various Git commands, to specify the old value of a reference. For example,
HEAD@{2}
means “where HEAD used to be two moves ago”,master@{one.week.ago}
means “where master used to point to one week ago in this local repository”, and so on. […] This command manages the information recorded in the reflogs.
If you see it’s out put the first time, it might look like a little bit too much information in one place (especially in bigger projects). An example output might look like this:
c125709 HEAD@{0}: rebase finished: returning to refs/heads/TEST01-extend-text-file c125709 HEAD@{1}: rebase: TEST01 added second change to text.txt b1eb54c HEAD@{2}: rebase: TEST01 added first change to text.txt 58751f9 HEAD@{3}: rebase: checkout master 05e3c7c HEAD@{4}: checkout: moving from master to TEST01-extend-text-file 58751f9 HEAD@{5}: commit: Fixed laster internal changes 42bc3db HEAD@{6}: commit: Also added my internal changes ea0ed79 HEAD@{7}: checkout: moving from TEST01-extend-text-file to master 05e3c7c HEAD@{8}: commit: TEST01 added second change to text.txt 386cd47 HEAD@{9}: commit: TEST01 added first change to text.txt ea0ed79 HEAD@{10}: checkout: moving from master to TEST01-extend-text-file ea0ed79 HEAD@{11}: commit: Added some changes to Text.txt a1aa182 HEAD@{12}: commit (initial): Added Text.txt
With this information, you just have to search for the original commit. In this case, it’s line 7 where the “Internal” word has been introduced. With
git show 42bc3db
you can see the changes which have been made in that commit. Personally, I prefer to use a Git client like SourceTree for this, because it has a better visualisation. (Using a Git client, you can easily hand over the commit hash and see all differences). Now you can compare this to your current code and maybe fix your problems.
An alternative solution is to take a look at the upstream. E.g. if you’re using Bitbucket or GitHub, you can directly see the commits in your browser. But this is just possible if you haven’t pushed your rebase to the upstream before (as I did with my rebase). Otherwise it might be that the commit has already been removed from the upstream.
Further links:
http://stackoverflow.com/questions/134882/undoing-a-git-rebase
http://www.ocpsoft.org/tutorials/git/use-reflog-and-cherry-pick-to-restore-lost-commits/