Most standardized git workflows are not suitable for real agile teams dealing with continuous delivery and constant changing of short term goals.
Most flows assume following a plan, over responding to change, like having planned releases, rigid process phases, waterfall-style.
Agile accepts those changes as normal. Small companies usually don’t have the luxury to make planned releases, stop development, test the release thoroughly and release only what was planned a month ago. And that’s fine. At the end, what drives our business? Our customers, and releasing new features before the competition, without causing problems.

The most used git workflow is git-flow described here:
This model suggests creating two long lived branches: develop and master. Master is the stable version (reflects the production version). Develop is the development branch, where new features are prepared.
At certain intervals a release is scheduled in order to stabilize develop. So a release branch is taken from develop and the stabilization is done there. Some people may continue working on develop if they work on a feature to be release later. You create topic branches for hotfixes and new features. Hotfixes are integrated into master (and release, if we are during a release), then back-ported into develop. Completed features are integrated into develop.

A first problem can be seen here: teams do not finish their jobs at the same time. Planned releases are fine in theory, but in practice some features from develop are far from done, even if at the time of integration into develop that feature seemed done. Taking a branch from develop and trying to finish them on release would be a bad practice, because releases should take 1-2 weeks and only bug-fixing should be done there, not development.

A second problem with this approach is that you can’t know for sure that only those planned features need to go live. I saw numerous cases where the client will push for a new feature (for him this is usually a key missing piece, so he doesn’t see it as a feature but as a bug) and if the customers is really important, we will try to satisfy him, in the agile spirit.

A third problem is that features are assumed to be independent of each other, which are not. One can introduce a change that makes integrating another feature impossible. Further more, the flow recommends (not forces) that features are developed locally, so this means no integration with other branches. For experienced people this has a big “merge conflict” sign on it, high probability for regression problems due to code incompatibilities and it’s ussually a pain to integrate.

What happens now with the release? We have a release branch with an incomplete feature, probably due to a new feature not originally part of the plan. We will probably throw away the release branch after we see that is highly unstable, and if we were smart enough to work on feature branches (the cold truth is that most people work on dev) we will integrate the completed feature branches into another release branch. But release takes longer than planned because one feature is not really done, the client makes some changes, because he realized he needs something more, but you can’t wait to finish that to release another important feature. On top of that, we have applied some hot-fixes taken from master, that need additional tests to ensure compatibility with the code on release. What a mess!

The solution is not to take the methodology all persons are hyped about now and try to adapt the business to this development workflow, but to create the best suitable workflow for the current business, especially when you can’t control the business side.

I am suggesting a methodology that incorporates instructions about QA as well, not only how branches are made and integrated with each other. It’s a release methodology, based on a basic feature branch flow.


  1. For each feature or hotfix we create branches from master.
  2. Internal Testing is done on the feature branches, which are kept in sync with master.
    This is the same thing as testing on the integration to master. Internal testing uses the dev environment.
  3. Once a feature is completed and tested by QA, the feature branch gets integrated into the UAT branch.
    UAT – User Acceptance Testing is a special environment very similar to production, usually sharing the same machine as the production, but using a copy of the database.
  4. Client acceptance is done on the UAT branch which is kept in sync with master.
    Clients confirm that features on UAT comply with their specifications.
    UAT and master accepts only merges, not commits or cherry-picks..
    Steps 3 and 4 can be skipped if the feature is minor and does not need acceptance.
  5. The accepted features get merged into master
  6. Automated tests should run on each build at least for UAT and Master and avoid update if they fail

As in the gitlab flow we are using long-lived branches for different environments like UAT but we don’t need a new branch for production, because we can safely use master. And we do not merge from pre-production (our UAT) into master, because then UAT would be a release branch, and we would need to stabilize it and handle all the problems we previously had. UAT for us is just a demo as close as possible with a possible integration into production, and a place to see how features interact with each other. Completed features are merged from their feature branches, not from UAT, which may contain partially done features.

If you fully understand how git rebase works, you  can develop features locally and rebase on master until you publish the branch. You will usually publish the branch if the feature is done or when you are collaborating with someone else on this branch.

Share This:

Join the Conversation


  1. Hi a bit confused about the diagram here, though I think I get it. Why is UAT pointed to master? Thought this said features are to be merged into master , not UAT. Is that right?

    Also at the bottom, you quickly mention rebase. But the strategy seems to imply we are to merge the code. Would this better suited rebase or merge and if so how would the branching look like?

    I can see this strategy will work for more than just UAT, but for any reason where you would want a separate integration/testing environment from the mainline.

    Would the UAT branch be a long standing branch that never gets merged anywhere? What happens if UAT falls too far behind master and new branches from master can’t handle the conflicts?

    Is there a way in git to force a branch to only accept non-ff merges? Seems like this would be nice to have for master and UAT.

    1. Yes, there is a mistake in the diagram because the arrow should not point a merge from UAT to master. This is mentioned clearly in the article. I noticed this mistake, but didn’t had the time to fix it. 🙂
      You will use rebase only for local branches, to avoid seeing those ugly merge commits in the history. Using rebase for published branches is to be avoided.
      Yes, you could have more UAT environments for testing how features interact, but this means you have a release plan.
      A simpler approach is to put the feature-to-master integration branch on the UAT environment and merge that pull request into master if tests went fine. But this means you are not testing how changes from one branch affect other branches. If QA tests them one by one and is fast, then it is ok to use this simpler method.
      Yes, UAT will never get merged anywhere. It can’t fall behind because you are constantly merging from master into it.

      1. On rebase – Once you have merged feature into UAT, you should not be able to rebase feature with master anymore, can you confirm? I’m thinking that rebases are always fine until the feature gets merged into UAT. Then if fixes need to be made to the feature as result of UAT, they need to go on the feature branch and then merged again into UAT. If feature is rebased at this point, could this confuse GIT?

        On more environments – Not suggesting one UAT environment per feature, but rather other environments with the same “Idea” as UAT but the target audience isn’t the client. It is other DEVs, business analysts, QA team, or anyone who needs an integration environment with in flight code which may not make the cut for the next release. Basically I’m saying the purpose of the Environment is for verification and integration testing, and only stable features can be chosen to go back to the master. It doesn’t necessarily need to be a UAT environment or Prod-like environment, it can be extrapolated to other use cases. Would you agree with the principle?

        Also, would it be advisable to devs never really to merge directly to Master, skipping UAT? Because what happens if, as a developer merges master into their feature, and the hotfix in master never went into UAT (skipped UAT), and then the developer goes to merge their feature to UAT, but now has some additional history from Master UAT does not have. Does this open up the possibility they might have to deal with a merge conflict on the code they themselves have not even touched at all?

  2. Hi

    Whilst I agree that most real life scenarios don’t fit neatly into the git flow methodology, I think the issues described in the first and second problems can be overcome with careful management of the pipeline.

    Whilst it is certainly true that teams often don’t finish at the same time, we can still release without committing incomplete feature branches to develop. In the spirit of agile, there is no reason to hold up releases because some features are taking longer than expected. The same is true for the second problem… Why would we hold up delivery of features that are complete because some aren’t?

  3. The “first problem” described is not true if you follow gitflow as originally described by V. Driessen on his post. This is because, features branches are not supposed to be merged into develop unless they are finished. Work in progress should stay on it’s own branch. Develop branch should only accept what is planned to go into the upcoming release. But then that comes another problem and that’s what I don’t like about gitflow: major features, can become too different from other work and then major conflict issues could arise when time comes to merge. This can also be solved by frequent rebases from the developer who is working on that feature. Marking use of feature toggles and incorporating codebase from new features into develop branch could be a better approach instead of putting a feature branch in isolation for such a long time. Sometimes we could have a feature that is actually completed and requires not further code changes, but external parties are not ready for it, then, when it comes time to merge it, we might have conflicts even when no other code changes were done on the branch.

    1. Integrating only finished features sounds fine in theory, but in practice we had numerous problems in different teams where developers genuinely think the feature is done, but it is actually not done. The definition of “done” of developers is generally different than the actual definition of “done”. I can get into details, but the problem is real in a real-life situation, when multiple features are developed simultaneously.
      If developers don’t use the “develop” branch as integration branch and only merge there the “finished” branches, meaning use develop as a pre-release, they will end up with many conflicts. Integrating often is equally important when collaborating.
      Yes, feature toggles, do help and that’s why for projects where 80% of a team works together, we usually switch to using a common integration branch and protect features with feature toggles (local or remote experimental branches still exist, but they are highly volatile). The integration branch is always kept in a ready-state and we usually release often when requested without worrying that something is not finished. But this approach is less optimal if features are isolated from each other (part of different projects), because the integration branch has a higher risk of breaking, and the common feature based flow works better in this case.
      You can have more integration branches for different projects, or even integration branches containing other integration branches to be able to test things together.
      My point is that this simplistic GIT flow is purely theoretical, and has major practical flaws. I saw many places trying to implement that ad-litteram but adapted it to what I am describing.

      1. “If developers don’t use the “develop” branch as integration branch and only merge there the “finished” branches, meaning use develop as a pre-release, they will end up with many conflicts.”

        This is not the case if developers continuously merge updates from develop into their feature before merging them back in?

  4. Outstanding. There are so many cheerleaders for gitflow I’m going insane. We tried it and became only frustrated with it. We stuck through it only because of it’s popularity. We moved to something similar to this method and it works awesome. Please developers, don’t follow “best practices” blindly just because it’s popular. That is how you get burned.

    1. Robert, why don’t you share your model so we can see it, curious how you solved your issues.

Leave a comment

Leave a Reply to Lucian Sabo Cancel reply

Your email address will not be published. Required fields are marked *