Skip to content

Instantly share code, notes, and snippets.

@parlarjb
Last active August 29, 2015 14:06
Show Gist options
  • Save parlarjb/7b5688ad4b9bd0b3adc8 to your computer and use it in GitHub Desktop.
Save parlarjb/7b5688ad4b9bd0b3adc8 to your computer and use it in GitHub Desktop.
Our git flow process

Git workflow

Introduction

On Cloud Atlas, we switched to a heavily modified version of the “git flow” workflow, based on http://nvie.com/posts/a-successful-git-branching-model/ and https://www.atlassian.com/git/workflows#!workflow-gitflow

The basic premise is this: We run one main branch, develop, and whenever we want to begin preparing for a release, we create a Release_x.y.z branch off of develop. It is these Release_x.y.z branches that get deployed to staging and then production.

All new feature work we’re going to do starts by creating a branch off of develop.

If you're brave you can jump straight to the TL;DR

Why use this process?

The basic process we've used in the past was to have a single master branch, do new feature development on various feature branches (which all branch from master), then merge those back into master when they are ready. To deploy to production, we'd take whatever was on master at a given point in time, and push that out.

This can work well, but as team size increases, it can cause three particular problems:

  1. If you find a bug in production that needs an immediate fix (i.e. a hotfix), how do you do that without bringing in all the other work that has gone into master since the last production deploy?
  2. When you decide that you want to start preparing master for a production push, no one is allowed to merge in Pull Requests anymore, because the state of master has to be frozen. This can lead to a large PR backlog as people working on feature branches can push PRs, but we can't merge them in
  3. This process basically requires that master is always ready to be deployed to production (i.e. a pure CI-CD environment). That is fine when individual feature tasks are independent of each other, but becomes much harder to manage in a topic-focused sprint, where multiple people are working on separate pieces of one single feature.

The process we're going to describe here was specifically set up to deal with those issues. It gives us a clean way to work on hotfix branches, merge them, and deploy them, without having to be worried about bringing in other development work that has happened since the last release. It also provides a clear demarcation for when work on a release will begin, and allows us to prepare a release without slowing down other development work that might be in progress.

The rest of this document will describe the process in detail. It's actually not very far removed from our traditional "merge into master and push to production" process, but it makes certain important steps explicit.

Feature branches

  • Must branch off of: develop
  • Must merge back into: develop

All new development work starts on a feature branch. We typically try to name these with their JIRA story in the name, for example EN-1045_server_images.

$ git checkout -b EN-1045_server_images develop

When you have completed your work and are ready to have others review, push this to github and create a new Pull Request (PR). In Cloud Atlas, we have the "default" branch configured to be develop. This means that when you create the PR, it should automatically be created saying

... wants to merge X commits into develop from EN-1045_server_images

After the PR has been reviewed and given the ok, someone other than you will merge it into develop via the "Merge pull request" button in Github.

Creating Release branches

  • Must branch off of: develop
  • Must merge into: develop

At some point in time, we decide that we're ready to take what's currently on develop and create a release from it. This means getting all the feature worked consolidated in a release branch and deployed out onto our production servers.

When it's release time, we create a branch off of develop called Release_x.y.z. (This is done via our Jenkins interface, and currently Marisol does this). This branch automatically gets deployed to our staging environment so QE can begin their validation process.

Because QE is doing validation, we can consider the Release_x.y.z to be in a beta state. If QE finds any bugs during testing, we'll have to create a patch with fixes. The best way to do this is to locally create a branch off of Release_x.y.z, which we can call Release_x.y.z-FIX1, commit your fixes to it, and send a PR to bring your fixes into Release_x.y.z.

$ git checkout -b Release_x.y.z-FIX1 Release_x.y.z
Switched to branch 'Release_x.y.z-FIX1'
$ fix stuff commit fix stuff commit
$ git push -u origin

NOTE: By default, when you push Release_x.y.z-FIX1 to Github and click "Create Pull Request", Github will try to create a PR against develop, not against Release_x.y.z. To solve this, when you're creating the Pull Request, click the "Edit" button at the top of the page, and it will let you change develop to Release_x.y.z

After Release_x.y.z-FIX1 (and any subsequent fixes) has been merged into Release_x.y.z, QE can re-test. If they're happy, we can deploy!

Merging releases to develop

The process of deploying a release is essentially telling Jenkins to push the active Release_x.y.z branch to production.

After a Release_x.y.z branch has been deployed, we must merge its contents back into develop. While the Release_x.y.z branch was originally based on develop, we might have had to put patches into it because of defects discovered by QE.

Merge Release_x.y.z back into develop by creating a new PR from Release_x.y.z against develop. If this PR can't be automatically merged, you'll have to locally fix any merge conflicts.

The recommended strategy for fixing these conflicts is to create a branch off of develop, called develop-merge_x.y.z. Then in that branch, do git merge Release_x.y.z. This will cause merge conflicts. Fix those, commit, and push develop-merge_x.y.z to github. Create a PR of it against develop, and it should be able to merge cleanly. This also gives other people a chance to verify that you correctly fixed the merge conflicts.

For example:

$ git co -b develop-merge_x.y.z develop
Switched to branch 'develop-merge_x.y.z'
$ git merge --no-ff Release_x.y.z
You'll get merge conflicts
$ fix fix fix the conflicts
$ git commit -a
$ git push -u origin

The git push -u origin will send develop-merge_x.y.z to github, and you can create a PR for it against develop. By definition this will merge cleanly, and will result in all of the Release_x.y.z work being brought in to develop

Hotfixes

  • Must branch off of: whatever Release_x.y.z is running in production
  • Must merge into: the next Release_x.y.z, if it's being worked on, otherwise into develop

Sometimes you need to make a quick fix to production. You can't do this against develop because then you'd have to bring in all of develop just to get your one fix.

In this case we'll make a branch against whatever the currently deployed release branch is, and increment the "point" number.

We should reserve the "point" updates for hotfixes. i.e. to move to version 1.5.1 from 1.5.0 requires a hotfix branch. This means that all feature branches would incrememnt the "minor" number, i.e. 1.6.0

For example, let's say that the last branch we deployed to production was Release_1.5.0. We then create our hotfix branch off of this:

$ git checkout -b Release_1.5.1 Release_1.5.0
Switched to branch 'Release_1.5.1'
$ git push -u origin

This puts Release_1.5.1 on github. (Note that we might move to having Jenkins create this branch for us). Now you can start do to a "patch" branch against it to implement the actual fix:

$ git checkout -b EN-12345-Some-Fix Release_1.5.1`

Get the hotfix working on EN-12345-Some-Fix, and when it's ready, send it as a PR to github against Release_1.5.1 (remember to change the PR target in the github interface, because it'll try to create a PR against develop by default).

Once QE has signed-off on Release_1.5.1, they will deploy it to production. Your next step is now determined by whether or not there's also an active Release_1.6.0 branch in development.

  • If there is, then you have to merge Release_1.5.1 into Release_1.6.0, to bring in the fixes.
  • If there isn't a current Release_1.6.0 branch being worked on, then you merge Release_1.5.1 into develop

Either way though, you do this by creating a PR from Release_1.5.1 against either Release_1.6.0 or develop

In the first situation (merging into Release_1.6.0), note that the fix will eventually find its way into develop because Release_1.6.0 will get merged into develop after it's deployed.

Final Notes

Hopefully it's apparent that the process described here isn't too far removed from our original "branch into master and release from master" process. The main difference is creating Release_x.y.z branches from develop, and using those as the basis for any hotfixes that are needed in the future. The process thus acts more as a reminder of the steps that need to be taken, such as merging Release_x.y.z back into develop when it's ready to go out, and creating release patches against the Release_x.y.z branches.

There's definitely been concern in the past about adopting "gitflow" as a process. "gitflow" can seem overly complicated, and the original documents describing it don't show how it relates to a github-based workflow. When you distill "gitflow" down to its essence, all it's really about is creating release branches and hotfix branches, and remembering to merge the results of those back into the right places. With a github-based workflow, certain parts become easier or unnecessary, and lead to something roughly equivalent to what's been described in this document.

You may have noticed that nowhere in the process is a master branch used. Instead we use develop. This part doesn't matter. We could switch every instance of develop in this process to master, and everything would be the same. All that's important is we have a single branch that feature branches go in to, and is used as the start point for Release_x.y.z branches.

TL;DR

All new feature work happens on the develop branch, creating EN-12345 from develop, and merging it into develop when complete.

When we want to prep for a new release, create a Release_x.y.z from the current develop. If QE finds problems in Release_x.y.z, create Release_x.y.z-FIX1, send as a PR against Release_x.y.z, and merge when ready. When QE is happy, we deploy Release_x.y.z to our production environment, and then merge its contents back into develop.

Any hotfixes against production happen by branching off of the currently released production branch, and incremementing the point number: git co -b Release_1.5.1 Release_1.5.0. Create Release_1.5.1-FIX1, fix the bugs there, and send it as a PR against Release_1.5.1. When QE is happy with Release_1.5.1, deploy it as our new production branch. After the deploy, merge it into Release_1.6.0, if that release is being worked on. If Release_1.6.0 has not yet started, then merge Release_1.5.1 into develop

Terminology

We use lots of terms that need to be clearly defined for all of this to make sense:

  • develop branch: This is the branch you use as the basis for new work. If you're going to start feature X, you'd create your feature-x branch from develop
  • Release_x.y.z branch: A release branch is always created from develop, and it marks the full scope of work you intend to push to production. The release branch might need bug fixes/patches before it's ready to deploy to production, and those patches happen against the Release_x.y.z branch itself
  • hotfix: A hotfix is when we need to do a quick fix against something in production, and we can't wait for a proper release branch cycle (because we're not ready to pull in all the other stuff currently on develop). Hotfix branches are always created from the the Release_x.y.z branch that reflects production (i.e. the latest deployed release)
  • patch: Fixes we apply to a Release_x.y.z branch while finalizing the branch
@chriscantu
Copy link

@parlarjb @halkon - Its obvious you guys put a lot of thought and effort into this.

Freddy and I have been discussing how we can stream line the process for testing. I had some thoughts that I would like to run by you both. Would you prefer to sync up prior to the team meeting or just discuss it in the meeting?

@klamping
Copy link

@chris would you mind documenting what gets discussed re: testing process? I definitely want to bring this flow in for EOD and want to make sure I'm not leaving anything out.

@klamping
Copy link

The one thing I'm not sure of is the version numbering. Is there a point to denoting major/minor releases? Maybe just stick with an 'iteration' and a 'hotfix' release_iteration.hotfix -> release_3.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment