The main problem you are going to face is that when you are combining the feature branches to a release branch, you'll need to solve all the inter-branch conflicts. Merge conflicts are the easier ones, since they pop when you are merging specific branches and you can ask the branch owner to solve them(it's far from ideal though, since the branch is not fresh in the owner's memory). But not all conflicts pop us as merge conflicts - some create compilation errors or runtime bugs, and it's not as trivial to figure which feature branches have caused those.
A possible solution can be to shorten the release cycle - adding more rapid "sub-releases", e.g. twice a week. This will limit the number of feature branches you are merging on each sub-release, which in turn limit the conflict potential. This, of course, comes with it's own problems - a frequent release overhead, where the release master needs to choose which features to merge in each sub-release, and after the sub-release the developers need to merge/rebase their pending feature branches(and resolve conflicts).
At any rate, I think your fear of branching-from-develop
is unjustified. You are portraying develop
as some big playground where all developers push their unfinished scrabbles of untested code - and it's not true. The feature branches fulfill this role. develop
, while it might not need to be as stable and as rigorously tested as master
, should still have a certain level of stability - the primary rule is not to push to develop if it'll prevent the other developers from continuing to develop even if they merge/rebase develop
to their feature branches.
This essentially means that you don't merge a feature branch to develop
unless it passes automated tests(doesn't have to be the full suite - if you have a 10-minute suite that catches most bugs and a 5-hour suite that catches even the rarest of bugs, test the feature branches with the 10-minutes suite), so it should be OK to merge it to develop
.
Note that master
still needs to pass the 5-hour suite, and you have no guarantee a a merged feature branch won't break the 5-hour suite - but neither does your model provide such guarantee. The point is that even if a merged feature branch does break the 5-hour suite - it's still a branch you want in the next release(otherwise you wouldn't have merged it to develop
), and the solution is rarely to exclude the feature from the next release.
Update
To answer the asker's first comment to this answer:
When runtime integration bugs arise, the affected feature-set team will be assigned to correct it. If it is caused by code from features created by any of the other teams, fixes are made into pull requests to the offending feature branch. Pull requests are then reviewed by the team that owns that feature, merged in and then merged into the release package. The team that knows how a feature should work makes the fix, the team who owns the offending code reviews it.
This method of solving bugs has several drawbacks compared to solving them as part of the preparation of a feature branch to be merged into develop
:
The feature-set where the bug happens is usually easy in to figure in both methods. The actual changes that invoked the bug are trivial when branching from develop
and very tricky when branching from master
. The former only gives you a cue about you about who should be assigned to try solving the bug first, which is not as useful as the actual lead you get from the letter. At any rate, branching from develop
allows you to have both hints.
The responsibility is backwards. If anything, the owner of the offending code is the one who should fix it, since they know best what they are trying to achieve, and the owner of the feature-set is the one who should review it, because they know best how the different parts of the feature-set should interact with each other.
But the branch-from-develop
approach has an even better way to decide who will be the one to start solving the conflict - it's the one who tries to merge last!
Now, that claim might seem a little weird and arbitrary - it looks unfair to "punish" the developer who pushed last for being slow. But I believe they are the best choice for starting to solve the problem:
They are already in the context of the problem. This is the most important reason - being in context is crucial for solving problems, and entering context is hard. But the developer who pushes last is already in context, because that's the task they are working on. They have already build the mental model that can help them solve this problem.
They are available. They don't have something more urgent to do right now, because what they were doing was trying to merge their feature branch, and solving the conflict is required for merging the feature branch.
They don't have to actually solve the conflict entirely by themselves - just to be the first ones to look at it. When examining the problem they can decide some other developers need to be involved. Since they are in context, they are in the best position to tell who these other developers are. Also since they are in context, they can help bringing these other developers quickly into context.
That pull request into the offending feature branch will be a nightmare. The code in the feature branch works, because the other branch it was conflicting with is not part of it. So, you are sending a fix to a problem that's not yet there, that might have to relies on changes that come with the same code that introduced that problem. There is no sane way to do that without merging/rebasing the other branch(or the new release) into the feature branch - but if you do that you are just using branch-from-develop
with develop
having it's name replaced on each release.
The Git Flow assumes you only have a single supported release, with the master
branch always pointing to the latest release. Since you support multiple releases simultaneously, you cannot copy that workflow 1:1. Nvie's Git Flow is a very good example of a branching strategy, but you must adapt it to your needs. Most importantly, you will have multiple active release branches.
When you identify a bug, you will have to do some testing to determine all affected versions. It is not sufficient to write the fix first, then merge it back into the release branches as a hotfix. Usually, you'll end up with some continuous range of affected versions. Very old versions might not contain the bug, newer versions might have gotten that bug fixed accidentally. You will need to verify the bug on each version so that you can verify it is actually gone after the fix. If you can express the bug as an automated testcase, it's pretty straightforward to find the problematic commit via git bisect
, or to run the test for each release:
for release in 3.8 4.1 4.2
do
git checkout $release
if ./testcase >release-$release.log
then echo "$release ok"
else echo "$release AFFECTED"
fi
done
Now, you used to write the fix on trunk
/master
. This is problematic because the buggy part may have changed between versions, so a patch will not usually apply to an older version. In particular, code in master
might depend on any features available in master, which might not have been present in older versions. It therefore makes a lot of sense that a Git commit references its whole history, not just the change set. When merging back, it will pull in all the history it depends on.
Using cherry-pick
or rebase
ignores this history, and records a new commit with the same changeset, but a different history. As pointed out, this will not work if your codebase has diverged.
The “correct” solution is to write the fix as a hotfix on the oldest affected release. Then, you merge oldest release into the second-oldest release. Usually, a newer release would contain all commits of an older release, so this is OK. If things have changed, you now have a chance to manually resolve the merge conflict. Then you continue merging each release into the next-younger release until you are done. This maintains proper history and avoids a lot of unnecessary work, while only requiring effort that has to be expended anyway. In particular, this incremental merging gets you closer to the current development state in small steps.
As a diagram:
| o normal commit |
| x hotfix |
| ⇘ merging |
3.8 --o-----------------x
\ ⇘
4.1 o--o--o-----------x'
\ ⇘
4.2 o--o--o-----x''
\ ⇘
develop o--o--x'''--o--o
Best Answer
How Long should these branches kept for ?
The first answer that pops in my mind here is :
Why are you creating release branch in the first place ?
If you are aiming to be able to provide support to a version in production use then it should live as long as that version lives with your customers. (say LTS support, you could have multiple such branches, making things a tad bit more complex)
If you are separating stabilisation work from on-going new development then once the version is deployed you don't really need that branch anymore. Here this would hint at a continuous delivery pipeline. Any bugs gets their fix in the next release which would occur as frequently as possible. Some push this to considering the master branch as the stable branch deploying multiple times daily. Others will sync to their sprints and release every two to three weeks.
Should you merge it completely back into master ?
As you guess it all depends on why you adopted this branching strategy. for continuous delivery style, since the branch will never be used again then you should treat it just as you would any feature or bugfix branches, likely you would merge it back to the master and forget about it.
If however you are aiming for LTS style then you likely would not merge it back, especially if you have multiple such branches. Here you would apply the fix to all release branches and cherry pick from one of them to merge that fix back in master. In this scenario the last thing you would want to do is to treat it like any other feature branch where changes from master are pulled up into it and conflicts resolved there rather than in the master.
Without knowing more about your product life-cycle and your team's workflow it is difficult to come up with a more precise advice here.