As far as source control goes, Git is by far my favorite so far. None of the Visual Studio dependency of TFS, and a much simpler branching model than Mercurial or SVN. One of the most important parts of using Git in a team environment, is choosing a branching strategy - it can be the difference between a smooth project distribution between developers, and a nightmare of conflicts, mis-merges, and lost code.
The major things to consider in your branching strategy are:
- What permanent branches will you use?
- How will your temporary branches be broken up?
- How will you merge any temporary branches back into the permanent ones?
- How will you keep your permanent branches synchronized?
The answers to these questions get to the heart of your development teams' practices -
- Do you want to vet an entire project as ready to go live, each time you deploy? Or is it enough to vet new features and changes independently?
- Relatedly, are you willing to accept 'feature' releases as single packages that get pushed on a schedule, or are you likely to fast-track specific changes and split them from other changes?
- How in-control of the project's release schedule are you actually? As an agency, we're often at the whim of a client's needs - some clients are willing to take the 'this is untested, we shouldn't push it through the process yet' for an answer, and some are not. If a client is likely to jump in and try to direct things, the branching model should support that flexibility.
- Do your developers work together on all features, taking top-to-bottom roles such as UX, business logic, and database? Or do your developers silo onto a specific feature, handling the UX/logic/database for that task, while other developers work on different tasks?
Based on these answers, we can start answering the initial 4 questions I mentioned - and two major strategies have won out among Git developers for this process.
This is the simplest model I've seen, and it's the basis for all the others. Essentially, reserve the master branch for production-ready code. If it's in master, it's ready to deploy, and safe for people to make *other* features based on. Then, when you want to make a new change to the code, you make a branch off of master at the tip. You build and test your updates from within your feature branch, and when it's ready for others to review, you create a pull request (or, if you're in complete control of the code, simply merge) back into master.
If a feature branch is too out of sync with master, you can merge master's tip into the feature branch safely, since master is always representative of production code.
Pros:
- The basis for all others - simple, direct.
- Pretty much impossible to mess up - just don't allow commits directly to master.
Cons:
- No specific support for separated testing environments.
- No specific support for hotfixes or partially-releasing features.
- No permanent branches for wiring up automated testing to.
This model is the one that I most often use with my clients, and that I encourage the other devs at Wakefly to use with most clients. It builds on the basics of the GitHub flow from above, and introduces support for testing environments.
The concept here is, instead of treating Master as the only permanent branch, we take the above model and add in intermediary steps. For example, if a client had production, but also a staging and development environment, we'd create 2 permanent branches off of master: "staging" and "dev". At the start, these will be exactly like master, except for configurations that are environment specific - these get committed directly to the specific environment - the staging configs go to staging, the dev config goes to dev, etc.
Then, when it's time to do work, we'd create a new feature branch off of master. When it's ready to go to Dev, we merge the branch into the Dev branch. When it's ready to go to Staging, we merge *the feature branch* into Staging - note, in this model we never merge environment branches directly against eachother - this way, environment-specific files like configs and such, never get merged up the chain. In this model, any differences between dev and staging and prod, get reconciled during the merge.
And then, of course, that feature branch gets merged into Master only when it's ready for release.
With this model, it's easy to wire up continuous integration to each of the Environment branches automatically. And features that are on Dev, can easily be moved up to Staging, and from Staging to master, independently - Since you're not ever merging the environments together, if a feature suddenly needs to be fast-tracked, it's already built independent of other features and can be moved.
Hotfixes are handled by branching and pushing directly back onto Master - the idea being, that after a hotfix is deployed to Master, you'll merge that hotfix back into dev and staging by creating a new feature, that implements the fix in a non-hotfix (non-hack-job) manner.
Pros:
- Allows an easy view into the state of each environment, and allows easy environment-specific configurations
- Easy to set up continuous integration to deploy environments
- Features can be moved independently into each environment
- This works best when a single dev or team is responsible for a given feature, so they can follow that feature along the process.
Cons:
- Need to set up commit-rules to block merging environments together, or educate developers to respect that rule.
- Releases are not structured - there's no guarantee that feature A and B work together, since they may have been tested while the other was not on an environment.
- Hotfixes can be on master for a while before they make it to dev and staging - they just hitch a ride on the next feature that is built.
This branching model is somewhat famous and infamous among Git-familiar developers, as it is both comprehensive, and complex. The philosophy here builds on the above two, but is based on the concept of stable releases, where features are moved to each environment at the same time. This is better for an internal development team, which can actually go to bat to protect this flow - as an agency, you risk losing business by telling the client 'no, this has to wait for the next release', but internally you have much more leeway and accountability, which this flow protects.
The idea here is to combine branching, with tags.
So, like environnment branching, you create a Master which is the state of production, and a 'develop' branch.
Any time you're working on a feature, you branch off of *develop*, and back into develop - but only once that feature is believed to be production-ready, and confirmed for the next release. Once a release is finalized - in other words, once you've merged every feature you think is going onto the next release - you branch off of develop, and create a 'release branch'. This represents what *will* become the next release, and allows for bugfixing, release notes, that sort of thing, to be added in. Once it's ready, to deploy it you merge it back into Master (which actually is the deployment) and then also merge the 'release branch' back into develop, as well - this makes sure any bugfixes from that 'release branch' are included for future development.
This flow also supports hotfixes - branch directly from master, merge back into master, and then merge into develop, to include in the next actual release.
Pros:
- This supports hotfixes, stable releases, the works
- Many scripts exist to manage the flow - developers don't really have to know Git itself, just 'the process'.
Cons:
- Complex enough that people have written scripts to manage it - lots of merging between environments.
- Features may be being developed using prior releases' code - no way to update long-running features to latest version.
- A bit monolithic - lots of times where branches are 'locked down' - for example, if the next release is pending, develop is off-limits.
- Starts complicated, but gets even more complicated if you want "staging", "QA', etc as separate environments - not supported by the base model.