📜 ⬆️ ⬇️

ThreeFlow branch strategy

Of all my conversations with colleagues about different aspects of software development, one topic emerges more often than others. Why are there “more often” - it repeats over and over again, like a broken record - these are conversations on the topic of what GitFlow is bad for and why it should be avoided.

The article "A successful branching model for Git " describes a method that later received the name "GitFlow" became the de facto standard of how to start using Git in your project. If you search Google for something like " git branching strategy, " then this method will be described by the first link (and most likely by several of the following).

Personally, I hate GitFlow and in recent years convinced many development teams to stop using it, which, I think, saved them a lot of time and nerves. GitFlow makes teams organize code change management worse than it can be. But since this is such a popular method (at least in the search engine results), teams without sufficient experience who are looking for “something that works at least somehow” find it when searching quickly, and also see the word “successful” right in the title of the article with its description - well, they start to use it thoughtlessly. I want to at least slightly change this pattern of behavior, having described in this article a simpler and no less successful strategy for using Git branches, which I implemented in many teams. Often these teams tried to use GitFlow, but experienced problems that disappeared with the transition to ThreeFlow.
')
I call this strategy ThreeFlow because it has exactly three branches. Not four. Not two. Three.

Well, immediately a warning: there is no silver bullet, and ThreeFlow is not a panacea either. It does not always fit. I think it will work badly for embedded-development and for open-source projects. But she is very successful for situations where the entire project team works in the same company and there are no pull requests from external developers. Those. everyone in the team has full access to the code and all the necessary rights to write to the repository.

So what is wrong with GitFlow?


In short, “not so” with GitFlow is his idea of ​​creating a branch for each developed feature. Branches for features - the root of evil. All that these branches give are problems, problems and problems again. If you don’t take anything out of this article anymore, or stop reading it right here - just remember the thought that branching for features is terrible.

image

In fairness it should be noted that the original article speaks of branches for features, that they "usually exist only on the machines of individual developers, but not in the main repository (origin)." But in the same article illustrating GitFlow drawings show it differently. We see "origin" with branches of features. Moreover, I have seen a lot of development teams using GitFlow and none of them paid attention to the author's recommendation to use branches only on individual developers' machines. Everyone I saw used feature branches as a long-term tool that exists in “origin”.

There is nothing wrong with doing branches for features in your car. This is a good way to switch between tasks if the needs of your project require working on several of them at the same time. This is a good way to keep the master branch clean, in case you need to make a small fix without having to synchronize all your work with a remote repository. But I would go further than the recommendations of the original GitFlow and would strictly forbid the creation of feature branches to origin.

If you use long-existing branches for individual features, then the hell of their integration will be your permanent reality . Two engineers successfully work each on their own features, each in their own branch, nothing seems to foretell trouble. But none of them sees the work of the other. Even if they regularly synchronize their branches with the main branch of the development, they see only the commits of completed and completed features, but not the current work of each other. And then developer A infuses completing the development of his feature and pouring code into the main branch. Developer B takes away these changes and gets the classic problem “who is the last, he also rakes up conflicts”. He may have been late just a minute, but now he will spend hours trying to understand what Developer A has written here and how it all should be done. And the longer this “isolated” development was carried out in separate branches of features - the more pain and suffering there would be with merge.

Long existing branches for features is not a simplification of work at all. It’s just postponing problems for later. The main form of communication between developers is the source code. You can console yourself with the fact that you have regular stand-ups, planning meetings and retrospectives, but all this is not important. Imagine a rehearsal of the orchestra, where the musicians have been discussing for a long time how they will play some kind of piece, but then the conductor asks them to disperse and rehearse their parts separately. Will such rehearsals make sense? So with software development - work in feature branches is essentially an analogue of deathly silence in communications between developers. Branches for features are awful .

In addition, the branches for the features are scaled horribly. One developer who creates himself a branch for a feature for his own comfort is not a problem. But your team is growing, and each developer has a branch for each actively developed feature. Congratulations, you now have a problem for each pair of branches. Let you have only 8 programmers and each of them works on just one feature in its branch. And here you already have 28 (number of pairs) dangling communication lines. We add another developer with another branch - and now you have 36 "cliffs".

Using flags to enable features


Instead of using branches to design features, try using flags to turn them on and off. It's simple. Start the development of a new feature with the announcement of the boolean flag, according to which it will be included. Set it to false by default - and in this case call the old code, without the code for the new feature:

if(newCodeEnabled) { //   } else { //   } 

image The flag itself can be either hard-wired in the code, or put into an external config (possibly using something like Consul or Zookeeper ), which will give the opportunity to turn on and off the new functionality for testing or even in production. Project managers and customers love to see in front of them a product control panel with a list of features that they can turn on or off themselves, without the need to involve developers and rebuild the project.

When two developers work in the same (main) branch on different features, they create a flag for each of them. And they just commit the code regularly. The chances of a conflict in this case are minimal. Everyone can commit code when they see fit. Anyone can synchronize their local repository with the main one - and the out-of-sync will be minimal (certainly not more than one working day). Either there will be no conflict at all, or they will be minimal. It’s much easier to understand what your colleague has changed in this ten lines in the last hour than to rake out global changes in days or weeks, as GitFlow suggests.

And yes, if you write tests for your code (and you write them, right?), Then you need to test the code branch with the flag disabled, and the one where the flag is turned on. If you are developing two interdependent features - at the time of development you will need 4 tests for all their combinations. This sounds like a threat of complicating and slowing down development, but do not forget that after the development of new features, the “old” code blocks (and tests for them) will be deleted, so you will not get a geometric increase in complexity.

Flags for new features can be used more dynamically. You can link them to specific groups of users for beta testing or A / B tests.

When the development of the feature is completed and it is enabled by default in production, you can schedule a small, low-priority task to remove the old code and the flag itself. Or, if for some reason you want to retain the possibility of disabling features (problems with the stability of the new code, load control on the backend), you can not do that. In any case, it is important to consciously make a decision to remove or leave the flag and the old code - if you forget about it, then over time your code will acquire the moss of the old unused functionality, which will only distract developers and not bring any real benefit.

The value of the approach with flags for the inclusion of new features is simply impossible to overestimate. I guarantee that as soon as you start using flags for features instead of branches for them, you will never want to go back. In my memory, almost always the development of a large new feature in a separate long-lived branch sooner or later led to problems requiring the attention of several programmers at once and deep knowledge of Git. At the same time, the approach with work in one branch and flags for new features never led to any conflicts that could not be solved in a couple of minutes by one person.

So ThreeFlow


So, we figured out why branches for features is bad and what can we replace them with. Now we can talk about the ThreeFlow branching model itself, which follows from this.

image

In this approach, all developers work in the same master branch. If the feature is trivial, it is simply implemented and added by one commit. If the development of the feature takes some time, then the flag is added first (disabled by default) to activate it. The developer includes this flag locally to develop and test a new feature, but the code in the main repository still uses the “old” code branches. To add commits to master, use rebase. If you used a local branch to work on features, it should be moved to master, so we will not have any traces of this branch to origin.

That's all. This is how the whole development process happens. One branch, master. All code in it. Everything you need is turned on or off with flags. All developers have the same code, often synchronized with each other. Everything else in ThreeFlow concerns only the release strategy, not the development.

Releases


When the time comes for release (on schedule or when management says), a “cut” of the master branch into the release candidate branch is done. The same thread is used for all release candidates.

The purpose of this branch is to give the build that the QA team will receive to perform regression (and other) tests. Theoretically, the new features of this release candidate have already been checked by QA in the course of their development and inclusion, but perhaps the QA will want to double-check them in the release candidate.

To create a release candidate, you do something like this:

 $ git checkout candidate # ,  candidate   origin/candidate $ git pull # ,       $ git merge --no-ff origin/master $ git tag candidate-3.2.645 $ git push --follow-tags 

The reason for using the "--no-ff" flag here is because we want to create a merge commit (a new commit with two parents). One of his parents will be the previous HEAD of the release candidate branch, and the second will be the HEAD of the master branch. This will allow you to easily track in the history of who created the release candidate, and also what exactly was included in it (which commits of the master branch).

You may also have noticed that we have created a tag for the release candidate. A little more detail about this.

If bugs are detected when testing a release candidate, they will be corrected right in the release candidate's branch, the new release candidate will be marked there, and the changes with corrections will be sent back to master. These changes should also be applied with the "--no-ff" parameter, because we want to accurately show which code was moved between branches.

When a release candidate is tested and approved, we update the release branch so that its HEAD points to the HEAD release candidate branch. Since we have a tag for each release candidate, we can launch it into the release branch:

 $ git push --force origin candidate-3.2.647:release 

The "--force" parameter here means that we ignore all changes in the release branch and simply forcibly set its HEAD to the same commit that designates the last release candidate tag (candidate-3.2.647 in the example above). Notice that this is not a merge at all, but this is because we don’t need it here. We don’t want to complicate the story in Git, and indeed the only reason for creating a release branch is the theoretical need for an emergency fix found on a production critical issue. Yes, this "--force" will grind all the hotfixes in the release branch. But you know, if you release the next version of the product with new features at the same time, while another member of your team fixes production bugs - you have serious problems with project management and communications. They should be solved even before the start of all these dances around the branches and releases. Fixes in the release branch should be very rare and, of course, should be later in the release candidate and master branches.

The reason why we use "--force" rather than merge is that when merge is committed to HEAD, the release candidate branches and the commit to HEAD release branches may have different sha-1, which is not what we need. We do not want to create a new commit with the release, we want to call the release exactly the commit that was chosen as a release candidate, which was tested by the QA team and approved for release by those responsible for it. This is what "--force" does.

image

If you follow these recommendations, the story in your git repository will look very similar to the picture above, showing exactly what commits moved between branches.

Release notes


You can easily generate “release notes” for new releases. You just need to get the difference between the last release tag and the current release candidate tag. Since in the release branch we have something that once was definitely a release candidate, we can find out exactly how:

 $ git describe --tags release candidate-3.1.248 

Now that we know that candidate-3.2.259 is in our release candidate, we can get the difference between these two tags:

 $ git log --oneline candidate-3.1.248..candidate-3.2.259 

Well, or even simpler, without tags, just compare the HEAD of the release and candidate branches:

 $ git log --oneline release..candidate 

Applicable operations


Here are some commonly used operations when working on ThreeFlow. All examples assume that your local branches are correctly correlated with remote branches and contain relevant changes. If you are not sure about this, it will always be a good idea to do git fetch once again and then use names like origin / master instead of just master

How can I make a release candidate from the master branch?

 $ git checkout candidate $ git pull $ git merge --no-ff master $ git tag candidate-3.2.645 #optionally tag the candidate $ git push --follow-tags 

How do I release from a release candidate?

 $ git push --force origin <tag for the candidate>:release 

If for some reason you decided not to tag release candidates, you will have to do:

 $ git push --force origin candidate:release 

How can I find a branch in which there is some particular commit?
Sometimes people want to make sure that a particular change is included in the release candidate or release. Here's how to check it out:

 $ git branch -r -contains <sha of commit> 

How can I find the tag pointed to by the HEAD of some branch?

 $ git describe --tags <branch> 

How do I know which commits will be included in some new release?

 $ git log --oneline release..<tag of release candidate> 

or:

 $ git log --oneline release..origin/candidate 

How do I set up release candidates and releases?
Any project starts with the first commit. Usually this is something simple, like adding a readme file. I advise you to simply make the branches of release candidates and releases from this commit. What we need to get is the first merge-commit with two parents. This way we get the correct story. So, in general, any commit master branch will do. Why not take the first one?

 $ git branch candidate `git log --format=%H --reverse | head -1` $ git checkout candidate $ git push 

To make a branch for releases:

 $ git branch release $ git branch release --set-upstream-to=origin/release 

Questions


Isn't it described as a “cactus model”?
You might think that the branch strategy described in the article is very similar to the “ cactus model ” described by Jussi Judin (also as an alternative to GitFlow and also using the master branch for the entire work). Yes, for the most part the way it is. The key difference is that Judin proposes to move commits from the master branch to the release branch selectively (“cherry-picks”). I am totally against it. Selectively moving commits is an extreme measure that should be used last of all in a completely catastrophic state of the master and a great need for an urgent release. I prefer to use a move (rebase) rather than a merge. And avoid selectivity.

Another difference is the existence of release candidate branches in ThreeFlow, which I accept as the minimum necessary evil. Personally, my goal is to maintain the master branch in such a state that it was possible to poke a finger into each commit and immediately calmly put it into production. But I noticed that many commandos find it difficult and uncomfortable to work in this mode. People prefer to have a buffer in the form of a QA team, who need to give a build approved by developers (“take this one, not the one, that bad one”) and get feedback about its quality from them. And the ThreeFlow model gives them that opportunity. In teams that are well suited to product quality, the differences between the release and release branches will be minimal.

Isn't it described by GitFlow just without branches for features?
In fact, I explained this strategy to those who previously used GitFlow in a similar way: “You do not use branches for features, all development goes in the develop branch, which we will now call master, and what you call master we will call branch releases. The main idea of ​​ThreeFlow was to minimize the complexity. GitFlow encourages the creation of new entities (branches) for any reason (for features, releases, hotfixes). The larger the project and the longer it goes - the worse its story looks. ThreeFlow strives to minimize the number of branches - no branches for features or hotfixes. Features are written in the master, hotfixes roll on the release candidate, or even release. And instead of a bunch of release branches, we always have what we call the current release candidate and the current release. Only three branches. Is always.

We also do not need to invent a naming system for branches (we have only three of them and their names are constant): master, candidate, release.

There is always an answer to the question “where should I put my code?”. If this is a hot fix in production, release. If this is a bug fix in the release candidate - in the candidate. If this is the usual daily work - in the master.

What about review code?
If you have a rule to revise the entire code before it gets into the main development branch, then it will be logical to add another branch (let's call it develop - yes, steal that name from GitFlow, why not). So, all the development will go in it, and then the reviewer will transfer the approved commits from it to the master (well, or ask them to modify). Of course, it will be necessary to somehow track what was transferred and what is not, and this can cause difficulties. It must be admitted that strictly following the idea of ​​reviewing the code before the commit to the main branch may not work for your team when using ThreeFlow or will require further adaptation of this approach. I heard that people successfully used tools like Gerrit for such purposes, although I never used it myself.

What about code bases in which several artifacts are stored?
In many cases, code that actually contains several projects can be stored in one code base. These individual build artifacts require separate validation cycles by the QA department and will have separate release candidate versions. How will ThreeFlow work in this case?

It will work well. Most recently, I once worked in a similar project. We had one Git repository from which several different artifacts were collected and deployed. The solution is obvious: each artifact adds two branches to the repository. You still write all the code in the master and work on the features using the disable flags. To do this, you do not need to know how much and what artifacts will be collected from the repository. But it comes to release, and here each artifact needs its own branches for release candidates and releases: foo_candidate, foo_release, bar_candidate, bar_release. That's all.

It scales better than you think. In one of my last projects we had one big code base from which 4 different artifacts were collected. Some common code, something individually for each subproject - well, you understand. On the one hand - 8 branches for release candidates and releases, plus one master. But on the other hand, each artifact had its own separate team, and for each of them only their three branches were relevant, so that their total number was of little concern to them.

Is it possible to somehow avoid the recruitment of additional Git command arguments?
One of the features of the proposed approach is that almost every command used actually has additional arguments. Every time you do a merge, you need to remember to add "--no-ff". When you make a release and tag it - I advise you to use "--follow-tags" when pushing to save tags to origin. You can make these tags apply by default:

 $ git config --global merge.ff no 

Now you can use the merge command without the "--no-ff" parameter (it will be added implicitly)

Similarly with tags when pushing:

 $ git config --global push.followTags true 

You can also configure automatic rebase when pull:

 $ git config --global branch.master.rebase true 

You can even make all new branches automatically reuse when you pull if you use local branches to work on individual features:

 $ git config --global branch.autosetuprebase always 

You can also remove the "--global" key from the above commands, if you want to apply these rules only to the current repository, and not to all in general.

Can I use merge for a release branch?
Well, first of all, you are a free person and you can do whatever you want. I just describe a strategy that works well for me and some other people. It seems to me that it is better than GitFlow (because it is easier).

Secondly, yes, if you don’t like the idea of ​​pushing the "--force" key and losing some of the historical information, you can do a merge with the "--no-ff" key. Plus, there’s also the fact that you don’t need to memorize different ways to transfer a commit between branches. Just make yourself a merge --no-ff always, and that's all.

In fact, the first version of ThreeFlow described exactly this behavior, merging with the --no-ff option for releases. It worked fine, the story was well read. The only thing I didn’t like was that the build artifact from the release branch was not formally the same commit that was previously considered a release candidate and went through QA and approval for release. It turns out, we tested one, then did something else, and here it is another rel. Poorly. You can, of course, replace the merger with fast forwarding, but this also leads to the loss of information, and it’s not a fact that it is guaranteed to succeed.

In my opinion, push + force more clearly indicates that the content of the release is actually not a branch in the terminology of the chain of inherited commits and should not be interpreted as follows. This is just a pointer to the actual code that is currently running in production. And the release branch itself simply points to a series of tags that were once laid out in production. Well, since this is still a branch with the actual code, you can always make a hotfix for production right in it.

Summarize


Using Git without a coherent branching strategy is a dangerous business. So take this one:


image

That's all. ThreeFlow Git, .

? ? , "--force" , ? !

Source: https://habr.com/ru/post/345826/


All Articles