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
The thing to remember is that merging a fix also merges all its ancestors, so if you want to keep code in 2.0
out of 1.0
, your merges should always go from 1.0
to 2.0
, and never the other way around. That means fixes need to be made (or rebased onto) the oldest branch possible, then merged into the second oldest, then into the third oldest, and so forth. This takes a lot of discipline, as usually developers prefer to work on the bleeding edge, and only think about the maintenance branches as an afterthought.
Best Answer
Your problem goes a bit beyond the normal git workflows and can be handled in several ways.
If the problem found during UAT is really small and can be fixed "while the customer is waiting", then you could do a quick bugfix followed by a new UAT of f2 and a release of all three features.
If the problem is more substantial, you could revert the merge of f2 into develop, do a regression test to check that nothing broke and then release the remaining features.
If comments from the customer during UAT are expected to be rather common, then it would be a good idea to consider doing a (pre-)UAT with the customer before the feature is actually merged into develop. Then you can work out the kinks out of the feature before it can affect the release schedule of other features.
If multiple features-under-development are expected to influence each other, you could also create temporary branches in which these features are integrated with each other for doing a test on both of them.