tags:

views:

70

answers:

3

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?

A: 

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...

Tobias Kienzler
Well, to be honest, I backed up my whole repository before starting, so I'm not totally screwed, but I'd like to avoid having to redo the whole rebase thing again...
Benjol
My understanding (from here http://stackoverflow.com/questions/2689634/do-i-need-to-perform-a-commit-after-a-rebase) is that B and C's commits have been modified by the rebase, so that wouldn't work, or am I missing something?
Benjol
@Benjol: You're partially correct: the rebased commits are indeed modified versions of the originals (specifically, they have different parents, and possibly somewhat different patches) but the originals are still there as well. The easiest way to find them is probably `git reflog show FeatureA` - this is a little easier than picking through `HEAD`'s reflog and identifying which lines belong to which branches.
Jefromi
@Benjol cherry-pick will only apply the diffs, so unless the several Features include identical changes, the diffs (not the commits) are still the same
Tobias Kienzler
+3  A: 

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).

Benjol
A: 

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)

knittl
This will all work, of course, but as long as the old unrebased commits are still around in the repository, there's no need to re-rebase. (Just gives more chances for error and unnecessarily refreshes the commit dates.)
Jefromi