Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recovering 'old commits' from multiple git rebases

Tags:

git

git-rebase

I am aware of this question, but not to sure how to map it to my current situation. (Rebase is scary, undoing rebase is double scary!)

I started out with several different feature branches of my master:

master    x-x-x-x-x-x-x-x-x-x
             \       \    \
FeatureA      1-2-3   \    \
FeatureB               A-B  \
FeatureC                     X-Y-Z

I wanted to merge them all together and check they worked before merging back onto the top of master, so I did a:

git checkout FeatureB
git rebase FeatureA
git mergetool //etc
git rebase --continue

Then

git checkout FeatureC
git rebase FeatureB
git mergetool //hack hack
git rebase --continue

Which leaves me with

master    x-x-x-x-x-x-x-x-x-x
             \
FeatureA      1-2-3 
                   \
FeatureB            A'-B'
                         \ 
FeatureC                  X'-Y'-Z'

Then I corrected some bits that didn't compile properly, and got the whole feature set to an acceptable state:

master    x-x-x-x-x-x-x-x-x-x
             \
FeatureA      1-2-3 
                   \
FeatureB            A'-B'
                         \ 
FeatureC                  X'-Y'-Z'-W

My problem is that my colleagues tell me that we're not ready for FeatureA.

Is there any way for me to keep all my work, but also revert to a situation where I can just rebase FeatureC on to Feature B?

like image 797
Benjol Avatar asked Apr 22 '10 13:04

Benjol


People also ask

How do I recover a lost commit in Git?

To see those "lost" commits, open up a command line, navigate to your repository folder and run: Now, from the list, find the id of the latest commit that you want to recover (e.g. HEAD@) and run: Your commit history should be restored and HEAD should now be reset to your last lost commit!

What is REBASE in Git?

Git Rebase Rebase is another way to integrate changes from one branch to another. It represents the process of moving or combining a sequence of commits to a new base commit, which means that it is changing the parent of the first commit on your branch making it appear as if you’d created your branch from a different commit.

How do I revert changes to a specific commit in Git?

Just click “revert” on the commit. If you’re at the HEAD of your commit tree, and you’d like to do this without making new commits, and you haven’t pushed the change yet, you can hard reset your local branch to the old commit. The reason this needs to be a hard reset is because a soft reset would still include the unstaged changes from the revert.

How do I view the last 3 commits in Git?

Create a folder in your preferred location named git-squash and go to that directory. Create a file named .gitignore and make an initial commit. Then add a README.md file and commit it. Do the same for test.txt and test2.txt file. Connect the local repo with the remote. To view the last 3 commits, type git log -3.


3 Answers

This is my understanding of what the answer is, based on comments:

When you do a rebase, the commits on your current branch are 'undone', then 'reapplied', but actually, they are not undone, they are 'remembered'*, and reapplied with new IDs, so for example, if I look in git reflog show FeatureB, I get something like this:

7832f89 FeatureB@{0} rebase finished: refs/heads/FeatureB onto f4df3
efd3fed FeatureB@{1} commit: B
f3f3d43 FeatureB@{2} commit: A
2f32fed FeatureB@{3} branch: Created from HEAD

So as @Jefromi said, the originals are still there (the SHAs of the A and B commits in the reflog are not the same as the ones in git log, which correspond to A' and B').

Similarly, git reflog show FeatureC looks like this

32873ef FeatureC@{0} commit: W
17dafb3 FeatureC@{1} rebase finished: refs/heads/FeatureC onto 89289fe
893eb78 FeatureC@{2} commit: Z
a78b873 FeatureC@{3} commit: Y
e78b873 FeatureC@{4} commit: X
378cbe3 FeatureC@{5} branch: Created from HEAD

Again, the original Z, Y and X commits are still there

So, the solution to my problem is to create a new branch FeaturesBC off the HEAD of master (for example), then cherry-pick the commits FeatureB{2 & 1}, and then FeatureC{4, 3, 2}, and (possibly) W:

git checkout master
git checkout -b FeaturesBC
git cherry-pick f3f3d43 
git cherry-pick efd3fed 
//etc...

(It seems to have worked, I had to re-do some of the same merges, but it wasn't too bad)

Edit, from Jefromi:

Cherry-picking may not have been necessary. You can also simply recreate branches where the branches were before the rebase:

git branch FeatureB-old efd3fed
git branch FeatureC-old 893eb78

Or, if you want to throw away the rebased position of FeatureB and FeatureC, going back to where they were before:

git branch -f FeatureB efd3fed
git branch -f FeatureC 893eb78

Finally, note that if you like you can use the other notation provided in the reflogs - for example, FeatureC@{2} instead of 893eb78. This means "the second previous position of FeatureC". Be careful to only use this immediately after viewing the reflog, though, because as soon as you update the branch again (move it, commit to it...), FeatureC@{2} will refer to 17dafb3 instead.

As @Jefromi commented on my question:

You should probably have created a new branch off of master or featureC (called featuresABC, say), and merged each into it, leaving the feature branches intact. It's good to preserve the independent history of various feature branches.

* To be precise, the old commit objects are simply left in the repository. They will eventually be pruned, since you don't want a repo full of old dangling commits; this will happen the first time git gc is run and the commits are at least two weeks old (configured by gc.pruneExpire).

like image 79
Benjol Avatar answered Oct 11 '22 08:10

Benjol


If all else fails you can restart from master and git cherry-pick all of B's or C's commits to recreate those branches. I hope someone else already wrote a script if this is the only solution...

like image 38
Tobias Kienzler Avatar answered Oct 11 '22 07:10

Tobias Kienzler


you can use git rebase --onto <old-merge-base from B> A C to rebase everything from C up to A onto a point on master. it will leave you with:

master    x-x-x-x-x-x-x-x-x-x
             \       \
FeatureA      1-2-3   \
                       \
FeatureB                A'-B'
                            \ 
FeatureC                     X'-Y'-Z'-W

to find the point where you want to rebase onto, you can use a combination of git's reflog and git merge-base – but you can also rebase onto the merge base of A, to have history similar to the following:

master    x-x-x-x-x-x-x-x-x-x
            |\ 
FeatureA    | 1-2-3
             \
FeatureB       A'-B'
                    \ 
FeatureC             X'-Y'-Z'-W

(git rebase --onto $(git merge-base A master) A C)

like image 43
knittl Avatar answered Oct 11 '22 09:10

knittl