Stop cherry-picking, start merging, Part 7: Preventing a change from leaving a branch


Still continuing our exploration of using merges as a replacement for cherry-picking, here's another scenario you can now solve:

How do I make a change in a branch that will not propagate when it merges?

Suppose you have a develop branch where active development occurs, and you also have a release branch that represents the current release candidate. Bug fixes in the release branch are regularly merged back into the develop branch. But what if there's a fix in the release branch that you don't want to merge back into the develop branch?

For example, maybe the component in question has already fixed the problem in the develop branch, but the fix came as part of a larger refactoring of the component. You want to apply a targeted fix to the release branch, but that targeted fix should not merge back to the develop branch.

It turns out that you already know how to do this.

This is basically the same thing as Replacing the temporary fix with the permanent fix, just with different names for the branches.

Here's the original diagram from last time:

    apple       berry
    M1 ← ← ← M2   master
apple ↙︎     berry ↙︎
A ← ← ← P       patch
  ↖︎       ↖︎
    F1 ← ← ← F2   feature
    apple       apple

In the diagram from last time, we have a common commit A onto which which a master branch and feature branch have been committing. We create a patch branch from the common commit A and apply a fix to it (changing apple to berry), producing commit P. We merge that branch into the master branch (producing commit M2 with the fix), and we also merge it into the feature branch with -s ours to indicate that we don't want any code changes from this merge; we are creating it only for bookkeeping purposes. The line remains unchanged in the feature branch.

From what we saw last time, this means that when the master branch merges into the feature branch, the line will remain unchanged.

Okay, now rename the branches. Rename the master branch to the release branch, and rename the feature branch to the develop branch.

    apple       berry
    M1 ← ← ← M2   release
apple ↙︎     berry ↙︎
A ← ← ← P       patch
  ↖︎       ↖︎
    F1 ← ← ← F2   develop
    apple       apple

Again, we start with a common commit A onto which a release branch and a develop branch have been committing. We create a patch branch that contains the version of the fix that goes into the release branch and merge that into the release branch. We then take that same patch branch and merge it with -s ours into the develop branch, resulting in no code change.

When the release branch merges into the develop branch, the changes will be discarded because the patch commit P will be a merge base, and relative to that merge base, the release branch didn't do anything, and the develop branch reverted the change. Therefore, the result of the merge is a revert of P.

I've heard people say that "In git, there is no way to prevent a change from propagating during a merge." Well, maybe not, but I just did it.

What happens, is that the commit propagates during the merge, but we have prearranged the merge base so that the result of the propagation is "no change". Technically the commit propagated: After the merge of the release branch into the develop branch, you can ask for branches that contain commit M2, and it will say that the commit is present in the develop branch.

The git-theorists are correct in that the commit did propagate during the merge. The git-pragmatists are correct in that the effect of the propagation is no change to the develop branch. Both sides are correct. Everybody wins.

Comments (9)
  1. Simon Clarkstone says:

    This series is certainly giving me a better idea of how to think creatively in solving problems with Git.

  2. Rick says:

    I’m having trouble generalizing this technique to prevent the patch from being merged into multiple feature branches, all of which may potentially have different merge bases with the master branch.

    Can you explain how to handle the situation where you want to avoid merging the change into two different development branches?

    1. Vilx- says:

      Shouldn’t the same approach work? Create your Patch Branch and then merge it into all of the relevant branches one after another.

      1. Martin Bonner says:

        Specifically, create your patch branch from a commit that is old enough to be an ancestor of all the feature branches (probably the commit that introduced the code you are patching)

  3. Alan says:

    I’ve been enjoying the series. You’ve certainly convinced me to be far more wary of cherry-picking, but more importantly, I have a much more solid grasp of how merges happen, can reason about them better, and can potentially solve more general problems. Which, I suspect was the real thing you wanted to teach. :-)

  4. Martin Bonner says:

    How many more episodes are you planning of this? I want to point my office at the series when it’s done.

    1. Currently plan is for Friday to be the final installment.

  5. Bradley says:

    I’m loving all this technical git stuff, but part of me worries about implementing this on a large team, where one little mistake to how a branch or a merge is done, can cascade in to hours and hours of fixing things down the road.

    1. Richard says:

      The takeaway was in the title of the series – don’t cherrypick unless you really, really want to.

      It seems that cherrypick is really a tool for creating patch branches that contain the commits you actually want to bring over.

      That’s not what I thought. Thank you Raymond Chen!

Comments are closed.

Skip to main content