Cherry pick a range of commits with merge commits
Asked Answered
L

1

8

I need to cherry pick a range of commits, but it fails because in this range I have merge of some branches. Is it possible to do it without conflicts?

Liven answered 18/1, 2018 at 22:56 Comment(0)
S
18

Is it possible to do it without conflicts?

That depends on what's in the commits-as-converted-to-changesets, vs what's in your own current commit. It's also not the real issue that I would be concerned about.

... it fails because in this range I have merge [commits]

Presumably you mean that git cherry-pick complains, upon reaching the merge commit, that you did not specify a -m argument. If you do specify a -m argument (git cherry-pick -m 1 ...), Git complains that you did specify a -m argument for those commits that are not merges. This is indeed a conundrum: you must specify -m 1 while also omitting -m 1.

The immediate solution to this problem is to do the cherry-pick piecemeal: pick the first however many non-merge commits (in the correct order and with no -m argument), then any merge commits (in the correct order using the correct -m argument, which is almost always -m 1), then any non-merge commits that follow those, then any merge commits, and so on.

Consider, though, what each git cherry-pick actually does: It consists of merging "what they did" with "what you did", where "what they did" is the difference between the parent commit and the child commit, and "what you did" is the difference between the parent commit and your current commit. The parent and child here are the parent and child of the argument we give to the git merge command. If that argument specifies a merge commit, we must also specify which parent—it's no longer just the parent—and hence the need for the -m flag. But cherry-picking a merge means that we should take all the changes introduced by the other leg of the merge.

This means that in many cases, you will want to skip all merge commits when cherry-picking.

Suppose, for instance, we have a graph that looks like this:

...--A--B--C--D---E--I   <-- feature-A
      \  \       /
       \  F--G--H
        \
         J--K--L   <-- feature-B (HEAD)

Our current commit is commit L. We'd like to obtain, for some particular reason, the equivalent of cherry-picking commits F through H, as either one big commit or three small commits that we add atop L, giving:

...--A--B--C--D---E--I   <-- feature-A
      \  \       /
       \  F--G--H
        \
         J--K--L--FGH'   <-- feature-B (HEAD)

where FGH' is either a three-commit sequence (F'-G'-H') or a single commit that does the same thing as the other three.

We can get this by using git cherry-pick and instructing Git to copy commits F, G, and H. Note that no -m flag is required at all as none of these are merge commits. If we use -n to suppress committing each individual pick, and do our own git commit at the end, we get one big FGH' commit. (If we let git cherry-pick do individual commits, of course, we get F' then G' then H'.)

Or, we can instead cherry-pick using merge commit E. Merge commit E has two parents, namely D and H. If we look at the comparison between D and E, what we'll see as changes are all the changes imported via the F-G-H sequence. So we are likely to get, as a single commit, a copy of the F-G-H sequence. If we only want a single commit, that may be the way to go. It's just one git cherry-pick rather than three, so it's certainly easier.

There is a caveat, though: this automatically excludes any changes that already reached E via the C-D sequence. For instance, suppose the change from C to D is similar to the change from B to F, so that D includes everything that is in F. Then the difference between D and E is only the sum of the differences from F to G and from G to H. (To put this another way, B vs H shows what happened in the F-G-H sequence, but B vs D includes everything that happened in F. Hence D vs E effectively hides the F changes, as they're already in D.) Cherry-picking the merge will therefore get us a single commit that has the effects of G and H combined, but lacks F. This might be what you want. It might not be what you want. There is no single universal formula for this operation. You must inspect the commits in question and think about the problem, rather than blindly applying some process here.

In any case, though, you now have the tools you need to think about the problem and choose a solution.

Scold answered 18/1, 2018 at 23:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.