Cover image
Teams and Processes
14 minute read

Enhanced Git Flow Explained

Git provides basic branching operations, but advanced patterns are left up to the user. The popular “Git flow” branching model helps, but can also complicate some common procedures. Thankfully, a new variation retains the benefits, while streamlining everyday work.

Inadvertently causing damage with Git can be all too easy. Yet, the best way to use Git will always be controversial.

That’s because Git itself only details basic branching operations, which leaves its usage patterns—i.e., branching models—a matter of user opinion. Git branching models promise to ease the pain by organizing the chaos that inevitably arises as software developers make changes to their codebases.

Like many developers, you wanted something that would “just work” so you could get on with actual software development. So you picked up Git flow, a branching model often recommended to Git users. Maybe you were on board with Git flow’s logic at first, until you hit some snags with it in practice. Or maybe Git flow doesn’t seem like a good enough fit for you to adopt it. After all, there are countless variables at play, and no single branching model will work well in every situation.

Good news! A variation of the classic Git flow model, enhanced Git flow simplifies the more common maneuvers of the Git flow workflow while still retaining the main advantages.

The Splendor and Misery of the Classic Git Flow Model

I’ve been a strong advocate of Git flow ever since I discovered how it excels when developing a product that evolves in significant value increments (in other words, releases).

A significant value increment requires a significant amount of time to complete, like the two-week-plus sprints typically used in Scrum-based development. If a development team has already deployed to production, there may be trouble if the scope of the next release accumulates at the same place where production code lives—e.g., in the main branch of the Git repo they use.

While the product is still in the initial development phase—i.e., there’s no production and there are no real users of the product—it’s okay for the team to simply keep everything inside the main branch. In fact, it’s more than okay: This strategy allows the fastest pace of development without much ceremony. But things change in a production environment; then, real people start to rely on the product to be stable.

For example, if there’s a critical bug in production that needs to be fixed immediately, it would be a major disaster for the development team to have to roll back all the work accrued in the main branch thus far just to deploy the fix. And deploying code without proper testing—whether the code is considered half-baked or well-developed—is clearly not an option.

That’s where branching models shine, including Git flow. Any sophisticated branching model should answer questions regarding how to isolate the next release from the version of the system currently used by people, how to update that version with the next release, and how to introduce hotfixes of any critical bugs to the current version.

The Git flow process addresses these fundamental scenarios by separating “main” (the production or “current version” branch) and “develop” (the development or “next release” branch) and providing all the rules about using feature/release/hotfix branches. It effectively solves a lot of headaches from the development workflows of release-based products.

But even with projects well-suited to the classic Git flow model, I’ve suffered the typical problems it can bring:

  • Git flow is complex, with two long-lived branches, three types of temporary branches, and strict rules on how branches deal with each other. Such complexity makes mistakes more likely and increases the effort required to fix them.
  • Release and hotfix branches require “double merging”—once into main, then into develop. At times you can forget to do both. You can make Git flow branching easier with scripts or VCS GUI client plugins, but you have to set them up first for every machine of every developer involved in a given project.
  • In CI/CD workflows, you usually end up with two final builds for a release—one from the latest commit of the release branch itself and another one from the merge commit to main. Strictly speaking, you should use the one from the main, but the two are usually identical, creating the potential for confusion.

Enter “Enhanced Git Flow”

The first time I used enhanced Git flow was on a greenfield, closed-source project. I was working with one other developer, and we had been working on the project by committing directly to the main branch.

Note: Up until the first public release of a product, it absolutely makes sense to commit all changes directly to the main branch—even if you are a Git flow advocate—for the sake of the speed and simplicity of the development workflow. Since there’s no production yet, there’s no possibility of a production bug that the team needs to fix ASAP. Doing all the branching magic that classic Git flow implies is therefore overkill at this stage.

Then we got close to the initial release and we agreed that, beyond that point, we wouldn’t be comfortable anymore with committing directly to the main branch. We had moved pretty rapidly, and business priorities didn’t leave much room to establish a rock-solid development process—i.e., one with enough automated testing to give us the confidence that keep our main branch was in a release-ready state.

It seemed to be a valid case for the classic Git flow model. With separate main and develop branches and enough time between significant value increments, there was confidence that mostly manual QA would yield good enough results. When I advocated for Git flow, my colleague suggested something similar, but with some key differences.

At first, I pushed back. It seemed to me that some of the proposed “patches” to classic Git flow were a bit too revolutionary. I thought they might break the main idea, and the whole approach would fall short. But with further thought, I realized that these tweaks don’t actually break Git flow. Meanwhile, they make it a better Git branching model by resolving all the pain points mentioned above.

After success with the modified approach in that project, I’ve used it in another closed-source project with a small team behind it, where I was the permanent codebase owner and an outsourced developer or two helped from time to time. On this project, we went to production six months in, and since then, we’ve been using CI and E2E testing for over a year, with releases every month or so.

A typical Git commit graph when using enhanced Git flow. The graph shows a few commits on develop and main, and before their common commit, several date-based tags.

My overall experience with this new branching approach was so positive that I wanted to share it with my fellow developers to help them get around the drawbacks of classic Git flow.

Similarities to Classic Git Flow: Development Isolation

For work isolation in enhanced Git flow, there are still two long-lived branches, main and develop. (Users still have hotfix and release capabilities—with an emphasis on “capabilities,” since these aren’t branches anymore. We’ll get into details in the differences section.)

There’s no official naming scheme for classic Git flow feature branches. You just branch out from develop and merge back in to develop when the feature is ready. Teams can use any naming convention they’d like or simply hope that developers will use more descriptive names than “my-branch.” The same is true for enhanced Git flow.

All features accumulated in the develop branch up to some cut-off point will shape the new release.

Squash Merges

I strongly recommended using squash merge for feature branches to keep history nice and linear most of the time. Without it, commit graphs (from GUI tools or git log --graph) start to look sloppy when a team is juggling even a handful of feature branches:

Comparing the commit graph resulting from a squash merge strategy to that resulting from a merge commit strategy. The starting Git commit graph has its primary branch labeled "develop," with an earlier commit having "feature-D" and "feature-C" branching from it, and an even earlier commit having "feature-B" and "feature-A" branching from it. The merge commit result looks similar, but with each feature branch causing an new commit on develop at the point where it ties back to it. The squash merge result has develop as simply a straight line of commits, with no other branches.

But even if you’re OK with the visuals in this scenario, there’s another reason to squash. Without squashing, commit history views—among them both plain git log (without --graph) and also GitHub—tell rather incoherent stories even with the simplest of merge scenarios:

Comparing the commit history view resulting from a squash merge tactic to that resulting from a merge commit tactic. The original repo state is given in timeline form, showing the chronology of commits to two feature branches coming from a common commit "x" on develop, alternating commits between the branches, namely in the order 1a, 2a, 1b, and 2b. Merge commit results are shown in two variations. In the commit history resulting from merging the second branch, merging the first branch, then deleting both branches, the story is chronological, but not cohesive, and includes an extra merge commit for the first branch. In the history resulting from merging the branches in order before deleting them, the commits are ordered, "x, 2a, 1a, 2b, 1b," followed by the merge commit for the second branch, which is not even chronological. The commit history from squash merging simply has a single commit for each feature branch, with the story of each branch told by the committer.

The caveat to using squash merging is that the original feature branch history gets lost. But this caveat doesn’t even apply if you’re using GitHub, for example, which exposes the full original history of a feature branch via the pull request that was squash merged, even after the feature branch itself gets deleted.

Differences from Classic Git Flow: Releases and Hotfixes

Let’s go through the release cycle as (hopefully) it’s the main thing you’ll be doing. When we get to the point where we’d like to release what’s accumulated in develop, it’s strictly a superset of main. After that is where the biggest differences between classic and enhanced Git flow begin.

Git commit graphs as they change when performing a normal release under enhanced Git flow. The initial graph has main diverging from develop several commits behind the tip and by one commit. After tagging, main and vYYYY-MM-DD are even with each other. After deleting local main, creating it at the tip of develop, force pushing, deploying, testing, etc., main is shown even with develop, leaving vYYYY-MM-DD where main originally had been. After the deploy/test cycle, staging fixes on main (eventually squash merged into develop), and meanwhile, unrelated changes on develop, the final graph has develop and main diverging, each with several commits, from where they were even with each other in the previous graph.

Releases in Enhanced Git Flow

Every step of making a release with enhanced Git flow differs from the classic Git flow process:

  1. Releases are based on main, rather than develop. Tag the current tip of the main branch with something meaningful. I adopted tags based on the current date in ISO 8601 format prefixed with a “v”—e.g., v2020-09-09.
    • If there happened to be multiple releases in a day—for example, hotfixes—the format could have a sequential number or letter tacked onto it as needed.
    • Be aware that the tags do not, in general, correspond to release dates. They’re solely to force Git to keep a reference to how the main branch looked at the time the next release process began.
  2. Push the tag using git push origin <the new tag name>.
  3. After that, a bit of surprise: delete your local main branch. Don’t worry, because we’re going to restore it shortly.
    • All of the commits to main are still safe—we protected them from garbage collection by tagging main on the previous step. Every one of these commits—even hotfixes, as we’ll cover shortly—is also part of develop.
    • Just make sure only one person on a team is doing this for any given release; that’s the so-called “release manager” role. A release manager is usually the most experienced and/or most senior team member, but a team would be wise to avoid any particular team member taking on this role permanently. It makes more sense to spread knowledge among the team to increase the infamous bus factor.
  4. Create a new local main branch at the tip commit of your develop branch.
  5. Push this new structure using git push --force, since the remote repo won’t accept such a “drastic change” so easily. Again, this isn’t as unsafe as it may seem in this context because:
    • We’re merely moving the main branch pointer from one commit to another.
    • Only one particular team member is doing this change at a time.
    • Everyday development work happens on the develop branch, so you won’t disrupt anyone’s work by moving main this way.
  6. You have your new release! Deploy it to the staging environment and test it. (We’ll discuss convenient CI/CD patterns below.) Any fixes go directly to the main branch, and it’ll start to diverge from the develop branch because of that.
    • At the same time, you can start to work on a new release in the develop branch, the same advantage seen in classic Git flow.
    • In the unfortunate event that a hotfix is needed for what’s currently in production (not the upcoming release in staging) at this point, there are more details about this scenario in “Dealing with hotfixes during an active release…” below.
  7. When your new release is considered stable enough, deploy the final version to the production environment and do a single squash merge of main to develop to pick up all the fixes.

Hotfixes in Enhanced Git Flow

Hotfix cases are twofold. If you’re doing a hotfix when there’s no active release—i.e., the team is preparing a new release in the develop branch—it’s a breeze: Commit to main, get your changes deployed and tested in staging until they’re ready, then deploy to production.

As the last step, cherry-pick your commit from main to develop to ensure the next release will contain all the fixes. In case you end up with several hotfix commits, you save effort—especially if your IDE or other Git tool can facilitate it—by creating and applying a patch instead of cherry-picking multiple times. Trying to squash merge main to develop after the initial release is likely to end up with conflicts with the independent progress made in the develop branch, so I don’t recommend that.

Dealing with hotfixes during an active release—i.e., when you just force pushed main and are still preparing the new release—is the weakest part of enhanced Git flow. Depending on the length of your release cycle and severity of the issue you have to sort out, always aim to include fixes in the new release itself—that’s the easiest way to go, and won’t disrupt the overall workflow at all.

In case that’s a no-go—you have to introduce a fix quickly, and you can’t wait for the new release to get ready—then get ready for a somewhat intricate Git procedure:

  1. Create a branch—we’ll call it “new-release,” but your team can adopt any naming convention here—at the same commit as the current tip of main. Push new-release.
  2. Delete and recreate the local main branch at the commit of the tag you created previously for the current active release. Force push main.
  3. Introduce the necessary fixes to main, deploy to the staging environment, and test. Whenever that’s ready, deploy to production.
  4. Propagate changes from the current main to new-release either via cherry-picking or a patch.
  5. After that, redo the release procedure: Tag the tip of the current main and push the tag, delete and recreate the local main at the tip of the new-release branch, and force push main.
    1. You likely won’t need the previous tag, so you can remove it.
    2. The new-release branch is now redundant, so you can remove it, too.
  6. You should now be good to go as usual with a new release. Finish up by propagating the emergency hotfixes from main to develop via cherry-picking or a patch.

Git commit graphs as they change when performing a hotfix during an active release under enhanced Git flow. The starting graph has develop as the longest line of commits, with main diverging two commits earlier by one commit, and three commits before that, the a branch diverges by one commit, tagged v2020-09-18. After the first two steps above, the graph then has new-release where main used to be, and main is not even with v2020-09-18. The rest of the steps are then performed, resulting in the final graph, where main is a commit ahead of where new-release had been, and v2020-09-20 is a commit ahead of where v2020-09-18 had been.

With proper planning, high enough code quality, and a healthy development and QA culture, it’s unlikely your team will have to use this method. It was prudent to develop and test such a disaster plan for enhanced Git flow, just in case—but I’ve never needed to use it in practice.

CI/CD Setup on Top of Enhanced Git Flow

Not every project requires a dedicated development environment. It may be easy enough to set up a sophisticated local development environment on each developer machine.

But a dedicated development environment can contribute toward a healthier development culture. Running tests, measuring test coverage, and calculating complexity metrics on the develop branch often decreases the cost of mistakes by catching them well before they end up in staging.

I found some CI/CD patterns to be especially useful when combined with enhanced Git flow:

  • If you need a development environment, set up CI to build, test, and deploy to it upon each commit to the develop branch. Fit in E2E testing here also, if you have it and if it makes sense in your case.
  • Set up CI to build, test, and deploy to the staging environment on each commit to the main branch. E2E testing is quite beneficial at this point, too.
    • It may seem redundant to use E2E testing in both places, but remember that hotfixes won’t happen in develop. Triggering E2E on commits to main will test hotfixes and everyday changes before they go out, but also triggering on commits to develop will catch bugs earlier.
  • Configure CI in a way that allows your team to deploy builds from main to the production environment upon a manual request.

The recommended setup of CI/CD with enhanced Git flow when there's a development environment ("dev") in addition to staging ("stage") and production ("prod"). All commits on the develop branch result in a build deploying to dev. Likewise, all commits to main result in a build deploying to stage. A manual request of a specific commit from main results in a build deploying to production.

Such patterns are relatively simple, yet provide powerful machinery to support day-to-day development operations.

The Enhanced Git Flow Model: Improvements and Possible Limitations

Enhanced Git flow is not for everyone. It does leverage the controversial tactic of force pushing the main branch, so purists may resent it. From a practical standpoint, there’s nothing wrong with it, though.

As mentioned, hotfixes are more challenging during a release but still possible. With proper attention to QA, test coverage, etc. that shouldn’t happen too often, so from my perspective, it’s a valid trade-off for the overall benefits of enhanced Git flow in comparison to classic Git flow. I would be very interested to hear how enhanced Git flow fares in larger teams and with more complex projects, where hotfixes may be a more frequent occurrence.

My positive experience with the enhanced Git flow model also revolves mostly around closed-source commercial projects. It may be problematic for an open-source project where pull-requests are often based on an old release derivation of the source tree. There are no technical roadblocks to sort that out—it just might require more effort than expected. I welcome feedback from readers with a lot of experience in the open source space regarding the applicability of enhanced Git flow in such cases.

Special thanks to Toptal colleague Antoine Pham for his key role in developing the idea behind enhanced Git flow.


Further Reading on the Toptal Engineering Blog:


Microsoft Gold Partner badge.

As a Microsoft Gold Partner, Toptal is your elite network of Microsoft experts. Build high-performing teams with the experts you need - anywhere and exactly when you need them!

Understanding the basics

Git flow is a branching model for the Git version control system (VCS). A branching model builds on basic Git operations, prescribing usage patterns meant to enable robust version management. Git flow was publicly announced in a blog article by Vincent Driessen in 2010 and has remained popular ever since.

The Git flow branching strategy excels when developing a product that evolves in significant value increments. It does so by separating "main" (the production or "current version" branch) and "develop" (the development or "next release" branch) and providing all the necessary rules about using helper branches.

You use Git flow by following its prescribed pattern. This lets your team get answer questions about how to isolate the next release from the version of the system currently in production, how to update that version with the next release, and how to introduce hotfixes of any critical bugs to the current version.

Whether you should use Git flow or not depends on your particular project. The main question to answer is, "Does the overall development workflow have enough automatic quality assurance to keep the main branch in release-ready state?" If the answer is no, then such a project will likely benefit from Git flow.

There's no single best Git workflow, because the answer depends on the particular project context. Some projects will benefit from Git flow or a variation of it, while others will be better with a trunk-based development approach.

Comments

Guilherme Battoni
Great article! Congrats!
François Dupire
Great article. Seems really like a less daunting version of Git Flow! I've got two questions, for my understanding. First, could we avoid deleting the main branch each time by doing a reset --hard develop or reset --hard <tag>? It seems to me that would work, but I may be missing something. Then, how does cherry-picking prevent conflicts compared to squash merges?
Daniel Ivanov
Thank you, Guilherme!
Daniel Ivanov
Hi François, thank you! Regarding your questions: - I'm afraid <code>reset --hard</code> won't work in this case at all since that command changes HEAD/working copy while we need to propagate changes in branches to the remote repo. - If you're asking about this phrase in particular - <blockquote>Trying to squash merge main to develop after the initial release is likely to end up with conflicts with the independent progress made in the develop branch.</blockquote> then it's not purely about cherry-picking vs squash merge. This phrase is about hotfixes: imagine we just did a release, what involved syncing main with <code>develop</code>, then testing & applying fixes as necessary in <code>main</code> and finally doing a squash merge of <code>main</code> to <code>develop</code> to ensure all these updates end up in <code>develop</code> also. Then some time later we had to introduce a hotfix to currently deployed release, so we update <code>main</code> branch, test & deploy that. If at this point we'll try to squash merge <code>main</code> to <code>develop</code> once again we may easily end up with conflicts especially if some changes were introduced to <code>develop</code> already. Upon squash merging no relation of commits is tracked down in Git in comparison to creation of a merge commit. That's the reason for recommendation to cherry pick hotfix commits made to <code>main</code> after the initial release. Hope that helps but feel free to ask more!
Dmitry Pavlov
Nice one!
Daniel Ivanov
Thanks, Dmitry!
David Bruchmann
No doubts about experience. For reproducing the workflow I'm missing some details, especially concerning "Create a new local main branch at the tip commit of your develop branch.". I know where I can find that info, just it's not part of the article.
Daniel Ivanov
Hi David, thanks for checking out my article! The full Git command log for that particular piece will be as follows: <code>git checkout develop git branch -D main git checkout -b main</code> But don't forget to tag the previous <code>main</code> first and push that tag, so it won't get lost!
David Bruchmann
thanks Daniel, that's helping. for completeness I add the commands for tagging: <code>git tag -a v1.2.3 -m "another tagged version" git push --tags</code>
Daniel Ivanov
That's right, so the whole Git command log for preparing a release will be something like this: <code>git checkout main git tag -a v1.2.3 -m "another tagged version" git push origin --tags git checkout develop git branch -D main git checkout -b main git push origin main -f</code> I should also admit that personally I'm not a Git console person, I prefer to work with a visual representation of a repo commits tree - just FYI!
David Bruchmann
that's an unexpected confession which explains why the code in the article is quite limited ;-) What keeps still to mention; in older repositories the branch-name might be "master" instead of "main". Thanks for puzzling everything together, like that it's easier!
Robson Sobral
Hi! I use a process similar. Delete, tag, squash... Are you ignoring the use of `rebase`? Why?
Daniel Ivanov
Hahah, welcome! Yeah, that's correct, we just decided to adopt the newer convention of <code>main</code> rather than <code>master</code> right away.
Daniel Ivanov
Hi Robson, thanks for checking out the article! Could you please elaborate more on what do you mean by "ignoring the use of rebase"? Thanks!
Matthew Stokes
Hi. Great article. Just had a quick question. For the workflow when doing a hotfix on a previous release, why go through the procedure of moving main? Could you not instead create a throw away hotfix branch from the previous release tag? Simplifying the git operations required for this procedure? I feel like I have missed something haha!
Daniel Ivanov
Hi Matthew, thanks for checking out the article! In regards to hotfixes there are 2 drastically different cases: - when there’s no active release: i.e. team works in <code>develop</code> and <code>main</code> corresponds to the release currently deployed to production. In such case doing a hotfix is super easy - just commit to <code>main</code>, deploy to stage & test, then deploy to prod. - during an active release: i.e. <code>main</code> was just recently moved to match <code>develop</code> and new release is in active QA & bugfixing phase. It's the most convoluted piece, so more on that below. If hotfix is not super critical the preferred way is to just include it into the release which is under QA. If that's not an option then there are several possible options including the one you outlined. However, the optimal way depends on team specifics & CI/CD set up. In the article we outlined the recommended way based on following key points: - CI is set up as described, i.e. every commit to <code>main</code> goes to staging env. To enable the approach you described you have to set up CI to also deploy to staging these throwaway branches. - Since release is in active QA other team members commit bugfixes to <code>main</code>. You'll end up with CI deploying both versions from <code>main</code> & throwaway hotfix branch as your team works on both. These back & forths may be also problematic in case new release contains some env-specific updates like DB schema migrations. If CI allows, deploys from <code>main</code> can be disabled temporarily to overcome that. - By moving <code>main</code> you clearly communicate that currently we're working on a critical hotfix and that's the priority number one - and it should be it since if it's not then just include hotfix in scope of a new release =) It doesn't block further work on the new release, though, team can commit to <code>new-release</code> branch, it just won't get deployed while QA focus is on the hotfix. Summing up moving <code>main</code> is a straightforward but perhaps a bit controversial way to address the issue. Obviously one can adopt any approach suitable for their project & team context.
Robson Sobral
Oh, I'm sorry! Instead of squash, I ask all my coworkers to do rebase and test before to merge. In case of a hotfix, we cherry-pick the commit and rebase main on dev. In the end of the day, we have a single straight line on git with fewer steps and a full history. Why you do not use rebase?
Daniel Ivanov
I'm afraid that's too little details to understand your whole workflow. Probably it's worth spending some time on preparing an article describing workflow you've been using, that will be a valuable contribution to community. Diversity of approaches enriches the ecosystem! In regards to squash vs rebase I believe the former is preferable exactly in case team isn't much interested in feature branches history since no matter how many commits were in a branch it lands in develop as a single commit with a message describing work done in the branch as a whole. And both techniques indeed provide nice linear history in comparison to merge commits.
Jim Nunnerley
Why don't you just merge develop into main when you are ready to release? This would seem the same to me and also give you a nice graph to see when that happened.
Human Vector
Thanks for the article Is this sentence a typo or am i just dopey? (Of course, both can be true) <code>With squashing, commit history views—among them both plain git log (without --graph) and also GitHub—tell rather incoherent stories even with the simplest of merge scenarios:</code>
Kevin Bloch
It reads correctly to me. Could you be more specific about what's not adding up?
Human Vector
Thanks for responding, Kevin. I thought your point is that the squash-merge you advocate yields a simple, linear development story. Yet this sentence says the opposite. Maybe there is a subtlety here I am missing.
Kevin Bloch
Thanks for getting back to us. You're right! I was reading it alone for grammatical sense, rather than in context. It should have read, "With<i>out</i> squashing, ..." It's now fixed. ✔
Daniel Ivanov
Hi Jim, thanks for checking out the article! Your suggestion is perfectly valid but since branching model is all about a ruleset of what to do in particular in each case it'll be a similar to EGF but different branching model. For instance, what to do exactly when there's a hotfix during a release under active QA? Should we move <code>main</code> to the latest commit prior to merge commit? What will be the best way to find such commit/should we tag it? What to do with the <code>main</code> under QA? Etc Personally I find graph with merge commits less informative and more convoluted while from my experience with EGF it was always nice straight lines of history for every delivery cycle (see the first pic in the article, it basically illustrates that).
François Dupire
Hi Daniel! Thanks for the reply :-). I'll play around with the branching model on a dummy project. Right now, I've still got trouble figuring out how those situations will work out :-)
Daniel Ivanov
You can invite me to the repo you're using, so we may have a substantive discussion ;)
François Dupire
Hi Daniel! I invited you to the repository :-) I only tested a simple release scenario so far, and it seems to me that both deleting/recreating the branch and reset --hard will work the same :-). I've reset the repository so that you can try it for yourself if you wish. Later on I'll try the hotfix scenario with cherry-pick and merge, but that's for another day :D!
Daniel Ivanov
Hey François, You're right - my bad, <code>reset --hard develop</code> is equivalent in this case, i.e. these Git commands: <code>git checkout develop git branch -D master git checkout -b master</code> can be replaced with just this: <code>git reset --hard develop</code> Assuming <code>master</code> is the current branch. Thanks for your interest & contribution!
тролльсостажем
Sorry, but not only this strategy can be named "enhanced" as it is flawed (for starters, anything based on <code>--force</code> is incorrect by design), it should be called disenhanced! The worst about this article is how it is built - impossible to understand how the author sees "classic" git flow, because how "disenhanced" git flow seems as product of not understanding the basics. HIGHLY NOT RECOMMENDED
Daniel Ivanov
Hi there! Disqus username of the author of the comment I'm replying to is in Russian and reads as "old-time troll", so I'm not sure if I should even jump in here =) I believe fear of <code>--force</code> is superstitious and is a result of the lack of the knowledge of how Git works. That doesn't mean one should strive to force push as much as possible, of course, it's just a right tool for a subset of cases. Article is structured in exact way to highlight downsides of CGF and how EGF could be of help, so the last point seem to be a potshot ¯\_(ツ)_/¯
Gary Mort
The problem with this sort of strategy is one developer on the wrong page can accidentally replace a large number of files and reset them back to old commits. Here is a solution for that. Assume your current release hash is XYZ and your current "staging" hash is ABC and is badly polluted. git checkout staging git reset --hard XYZ git reset --mixed ABC git add . git commit -m 'Synced staging with current release. All developers must re-submit any changed code not in the 1/19/2020 release' git push origin We use bitbucket, so to make the history clean up there, I then do the following: Through bitbucket, create a pull request from staging to master. Since the CODE is exactly the same there should be no file changes - it is merely a commit to merge git HASH history. If there is a file changed, you did something wrong. Merge the PR on bitbucket Through bitbucket, create a pull request from master to staging. Again the code is exactly the same or you messed up. We are merely sanity checking and ensuring all the commit hashes are in sync Merge the PR on bitbucket Production is now at commit: RRR Now, let us say you have a developer who, while working on their feature they started from further back in time, say a shared commit for production and staging to AAA. In their branch they did their work and then merged to staging when they reached ZZZ. All those code changes which were not in release 1/19/2020 cannot be merged via git alone - as far as git is concerned they already have been merged and then they were changed later. Instead, your dev has to do a change comparison: git diff AAA..ZZZ. They identify the files they changed and need and merely check them out: git checkout ZZZ my/changed/file.txt. Sanity check the changed. If you compare the changed file to commit ZZZ the same lines of code should be changed if you compare it to RRR. If this is not the case, then 2 developers edited the same file and this is equivalent to a "merge conflict".. However in this case the developer who wrote their code and knows what it does can determine which functions they modified - and only add those changed items into their next commit.
comments powered by Disqus