1

I have the following situation:

a--b--c--d--e--f--g--h--i--j  (dev branch)
    \     \     \
     x--y--z--q--r--s         (feature branch)

When I merge dev into feature, I discovered I lost many (or all?) of the changes on the feature branch. I'm guessing that too many files were touched on dev that overlap files on feature. Regardless of the reason, this is someone else's repository I'm working with (I don't have tribal knowledge of why this occurred).

So I am interested in rebasing feature onto dev in order to replay the changes. I figure I will capture the entire feature branch (back to commit x) but I am concerned about the merges into feature represented by commits z and r. I am afraid that as a consequence of the rebase - older code from the dev branch will overwrite newer code on the dev branch.

Is that a valid concern?

If yes, what would be the easiest way for me to sift out the unique changes on the feature branch and replay them on the dev branch? I'm not sure that cherry-picking is the right answer either. But if cherry-picking is the right answer, can someone explain the sequence of commands I should attempt to "get it right"?

Thanks.

Brent Arias
  • 29,277
  • 40
  • 133
  • 234
  • Possible duplicate of [Git workflow and rebase vs merge questions](http://stackoverflow.com/questions/457927/git-workflow-and-rebase-vs-merge-questions) – Tatsuyuki Ishi Mar 14 '17 at 00:31
  • Both rebase and merge should work here, please see the linked question for detailed comparison. – Tatsuyuki Ishi Mar 14 '17 at 00:32
  • It's not 100% clear, but it sounds like you had conflicts at the `z` and `d` merges and took `dev` instead of resolving manually. If that's the case and you only have 3 commits I would cherry pick them. It avoids the faulty merges, and handling conflicts one at a time may be easier than all at once. – dlsso Mar 14 '17 at 00:55
  • I de-conflicted the two 'd' commit names; that was a mere oversight. I also corrected the answer from @torek to match the corrections I made in my question. – Brent Arias Mar 14 '17 at 02:35

1 Answers1

2

While there are plenty of wrong answers, there is no single right answer. Note, however, that git rebase works by copying some commits, essentially,1 doing a series of git cherry-pick operations to make each copy. It is in general difficult to cherry-pick a merge, so git rebase simply doesn't bother: it just skips merges.2 This may be exactly what you want.

Suppose, for instance, that this is a literal transcription / accurate diagram of the commit graph, or at least the interesting part of it:

a--b--c--d--e--f--g--h--i--j   <-- dev
    \     \     \
     x--y--z--q--r--s          <-- feature

(except of course that each commit node has a big ugly hash ID instead of a simple one-letter name). If we copy the effect of x, y, q, and s to four new commits that add on to commit j, putting them on a new branch, and rename the old feature to old-feature, we get:

                             x'-y'-q'-s'  <-- new-feature
                            /
a--b--c--d--e--f--g--h--i--j              <-- dev
    \     \     \
     x--y--z--q--r--s                     <-- old-feature

These four copies can be made by running git cherry-pick on the four IDs for commits x, y, q, and s, after checking out commit j using the new branch name new-feature.

Note that git rebase chooses which commits to copy using, in essence, git rev-list <limit-hash>..HEAD, where <limit-hash> is whatever you give git rebase as its <upstream> argument. Choosing commit j suffices as this excludes the entire a-b-c-d-e-f-g-h-i-j chain from the selected commits. We simply need HEAD to identify commit s, as it will if we check out commit s using our new name.

Once git rebase finishes the copying, it moves the current branch's name so that it points to the last-copied commit, i.e., s'. So we don't even need a new name at all: we can just git checkout feature && git rebase dev and Git will copy the desired set of commits.

This does make the assumption that the graph we have drawn here is accurate. Also, note that the way git cherry-pick works is to diff each commit against its parent. That's why it's difficult to cherry-pick a merge: a merge commit has two, or sometimes even more, parents; which one should we diff against? (Git lets you provide an answer: git cherry-pick gives you the -m option to pick which parent to use if the commit is a merge. But we want, at least for this experiment, to throw away the merges, and git rebase does that for us.)

The drawbacks to rebasing, especially of long and detailed feature branches, are that we have to copy a lot of commits and we may get a lot of conflicts; and then anyone else who has the original commits needs to stop using them and switch to our new copies. There is a twisty maze of fiddly little steps that can go wrong. The advantage is that we get a simplified, cleaner history in the end, at least if we get all the fiddly maze of steppy twists, er, maze of steppy fiddles of twisty ... oh no, I've been eaten by a grue!


1Some rebases literally use git cherry-pick, and some do something equivalent instead.

2You can cherry-pick a merge, sort of; and rebase has the option to "preserve" merges; but cherry-picking a merge produces an ordinary non-merge commit, and it's not useful to attempt it in the first place, in the cases for which this is intended. Instead, a "merge preserving" rebase re-performs the merges, which has its own pitfalls. But none of that really applies here.

Brent Arias
  • 29,277
  • 40
  • 133
  • 234
torek
  • 448,244
  • 59
  • 642
  • 775
  • This is when changing careers, becoming a scuba-diver for Roto-Rooter, starts to sound attractive. Anyway, excellent answer. – Brent Arias Mar 14 '17 at 02:15