TL;DR: Using GitFlow, which commit do you tag to identify the build of your next release? If you tag the merge commit on main
, you'll need to rebuild and deploy something you didn't test on QA (and this contradicts "build-once, deploy anywhere"). If you tag the last commit on the release branch, your commits on the main
branch won't represent a release (and this contradicts GitFlow).
This question is specific to the GitFlow workflow. In this workflow, you create release branches that are merged into the main
branch. Supposing you have a QA (or "staging") environment, you would build the release branch and deploy it on that environment to do QA tests.
Once the release is ready, the process would then be this:
When the state of the release branch is ready to become a real release, some actions need to be carried out. First, the release branch is merged into master (since every commit on master is a new release by definition, remember). Next, that commit on master must be tagged for easy future reference to this historical version
So you're tagging the merge commit on the main
branch (e.g. v1.0.1
). In theory, you'll use that same commit to build your release (e.g. if you use docker, you would build an image that represents the state of the code on tag v1.0.1
).
However, this means that you're building a different version of your code, that you did not test on the QA environment: the commit/tag v1.0.1
represents the merge commit on main
, not the last commit on the release branch, so it was never deployed to the QA environment. It may sound like a small difference, but you're actually building your image at a different point in time, and this can introduce unexpected differences.
If you tag the last commit of the release branch, instead, you can deploy that exact build directly to production, knowing that you tested the exact same thing on QA. But then your commits on main won't represent a new release anymore (contradicting GitFlow).
Am I missing something here? How do you proceed to use GitFlow and still follow the "build-once, deploy-anywhere" idea?
References
Note: there's a similar question on SE but it doesn't mention the part about building images for deployment, so I think my question is more specific and not a duplicate.
2 Answers 2
I don't agree that tagging a merge commit means that you're building a different version of the code that was not tested.
If you have commit x
on your release branch and then merge that into your main
branch, you will have commit x
and merge commit y
on the main
branch. The contents of your repository on commit x
and merge commit y
will be identical. If you build by commit hash, unless your build products contain timestamps or commit hashes, the result should be identical if you build commit x
or merge commit y
. If you are very concerned, you could always tag the commit before the merge commit, which should be the last commit in the release branch, but I would consider this to be an unnecessary overhead.
In practice, teams that I've worked with that are still following a model like gitflow have moved away from some of the specifics of gitflow, primarily in two main ways.
First, most teams have dropped the concept of a main
or what gitflow calls the master
branch. They have also dropped the notion of a hotfix
branch. They only use develop
, release branches per release, and feature branches. This greatly reduces the overhead of merging changes between branches.
Second, many of the teams have switched to containerized applications. The build process creates containers from the develop
branch, release branches, and feature branches, and identifies them appropriately. The commit (and, if it exists, tag) from git is used in the container identification. Whenever a container passes all necessary (automated and manual) tests, it can be identified. The same container that was deployed to the test environment and passed can also be deployed to the production environment.
-
Thanks for the useful answer. You said: "The contents of your repository on commit
x
and merge commity
will be identical". This is true, but a build is not just the code. If you build the same code in two different moments, you can have different results (for example, the most recent build might break because some external dependencies introduced breaking changes between commitx
andy
). That's why I'd rather build and deploy the last commit on the release branch, rather than the merge commit frommain
. I would say that this applies to containerized applications as well.Kurt Bourbaki– Kurt Bourbaki12/18/2023 14:45:20Commented Dec 18, 2023 at 14:45 -
A question regarding dropping the
main
branch: how would you then use the release branches? I guess you branch offdevelop
to create a new feature branch and then you merge the feature branch back intodevelop
(as you would do with themain
branch in Github flow). When do the release branches come into play, and for what purpose?Kurt Bourbaki– Kurt Bourbaki12/18/2023 14:50:29Commented Dec 18, 2023 at 14:50 -
@KurtBourbaki External dependencies are not a concern of the branching model. When teams need to ensure tighter control over external dependencies, they should mirror those external dependencies into an artifact repository that they control. This can be beneficial to protect against missing dependencies or supply chain attacks, as well. By pulling build dependencies from a more trusted source, you can be sure that your builds will be identical as time passes.12/18/2023 14:52:11Commented Dec 18, 2023 at 14:52
-
@KurtBourbaki Without
main
, the release branches are used exactly as you describe. They come into play when teams need to support multiple releases at once. Perhaps v1.2, v1.3, and v2.0 are all supported and may need patches. You would be able to appropriately patch all the versions for security fixes or critical defects until the version is no longer supported. Depending on the changes, you can branch from the release branch or cherry-pick into the release branch to product 1.2.x, 1.3.x, and 2.0.x versions as needed.12/18/2023 14:54:58Commented Dec 18, 2023 at 14:54 -
your suggestion regarding external dependencies is cool, however, I've never seen it applied before (doesn't mean it's not good!). Usually, you would have a requirements.txt file (or whatever you use) that will be used to manage dependencies of a docker image at build time. If you build twice, dependencies will be resolved twice (in two different moments). Your suggestion is interesting though: do you know some open source project that uses this approach? Thanks again!Kurt Bourbaki– Kurt Bourbaki12/18/2023 15:06:53Commented Dec 18, 2023 at 15:06
You are correct. Hotfix branches screw GitFlow because the merge to master may not be the same as the merge to develop. Thus when you come to merge in your release branch, the post merge code may be different.
Here is an example.
master v1 has a bug in the email sender. I create a hotfix branch to fix it
Emailer.Send(Message m)
{
smtpclient.Send(m); //fixed! this was commented out!!
}
This merges into master fine the only change was removing the commented out line. But in develop we have already refactored the email sender so when I come to merge I have to resolve the conflict manually
Emailer.Send(Message m)
{
_client.Send(m, timeout); //this was commented out and renamed
}
I carry on with develop, branch to release v2 and finally come to merge release v2.2 into master.
I get a merge conflict, because the emailer class has been changed separately in both branches and I have to manually resolve it. When doing so I make a mistake
Emailer.Send(Message m)
{
_client.send(m, timeout);
}
Now the build from release v2.2 which was tested and works fine and the build from master tag v2.2 which will fail are from different codebases.
Explore related questions
See similar questions with these tags.
master
is a release. What I want to do is just reuse (in prod) the same build that has already been tested on the QA environment. Why does it involve CD? Could you elaborate?