gitarchitecturegit-mergerelease-managementtrunk

Release branch process - how to merge release branches


We are adapting to trunk-based development with release-candidate branches. (at the moment we are using trunk-based development, but without release candidate, which is not sufficient anymore)

There is nice description on google: https://cloud.google.com/architecture/devops/devops-tech-trunk-based-development

Trunk based development

The challenge we have is how to merge bugfix into several Release branches.

Imagine this situation:

  1. You create Release 1 (similar to 1.0.0 in picture)
  2. You do several changes to Trunk
  3. You publish Release 1 to Production
  4. You create Release 2 (similar 1.1.0 in picture)
  5. You do several changes to Trunk

Now you find out that there is critical bug in Release 1:

  1. You create bugfix from latest commit in Release 1
  2. You merge it back to Release 1 (thus creating 1.1) and then you merge Release 1.1 to Trunk
  3. You happily publish Release 1.1 to Production

So far all of the above is complient with the picture from Google.

However now is the question how to get the same bugfix to Release 2. (it does not matter if Release 2 is on production or not). You cannot just merge Trunk to Release 2 - some untested changes were made. So the recommended approach is to cherry-pick bugfix from trunk and merge it to Release 2. Altough that would work, it looks a bit human error-prone (not picking everything or not doing it correctly + the visibility of what is merged in history is also not easy to see).

I would personally merge directly the Hotfix branch (that originated from Release 1) to Release 2 (therefore creating Relase 2.1 with bugfix). However all references about trunk-based development with release branches are mentioning cherry picking from Trunk and not merging the bugfix directly.

Am I missing something? Why is cherry picking better/more used solution than just merging the bugfix?


Solution

  • This is the mistake, in my opinion:

    Now you find out that there is critical bug in Release 1:

    1. You create bugfix from latest commit in Release 1

    People do this for expediency, but the "correct" method is to create the bug fix as a new branch, whose parent commit is the commit in which the bug was introduced.

    That is:

                        tag:v1
                           |
                        I--J   <-- release-oriented-items-for-v1
                       /
    ...--B--C---------G--H   <-- mainline
             \       /
              D--E--F   [feature, originally]
    

    Now suppose the actual bug was introduced in commit E, during the implementation of the feature.

    The "correct" way to fix this is not to add fixes at (after) H and J but rather to add it immediately after commit E, like this:

                        tag:v1
                           |
                        I--J   <-- release-oriented-items-for-v1
                       /
    ...--B--C---------G--H   <-- mainline
             \       /
              D--E--F   [feature, originally]
                  \
                   K   <-- fix1234
    

    Branch fix1234 (i.e., commit K) can now be easily merged into every downstream release—well, as easily as any development since E makes it, anyway.

    (Read the next two paragraphs in parallel, and merge them in your head.)

    Note that if—as does occur in practice—it's necessary to make the initial hotfix as a follow-on commit to J, we can still cherry-pick that fix back (so that it comes just after E) to form the fix1234 branch, which we can then forward-merge into every release (including the v1 release branch, if only to show that yes, the fix is now included in future v1-based releases). And, if the bug no longer appears, you can choose to merge with -s ours, or not bother to merge at all, based on how you feel about using the "merge indicates bug is fixed" idea.

    See also Raymond Chen's series of blog posts describing this same idea. Note that this general plan, "go back in time and fix the bug and then merge the fix forward in time", works for all the bug cases, and doing the "cherry-pick back in time so that we can merge forward" technique works in all the bug cases. The fix need only be merged in any commit that is a descendant of the bug, and does need to be merged in every release that is a descendant of the bug, and the fact that it is or is not merged tells you whether the bug is or is not fixed.