Source Control
We typically use git repositories to manage the source code that we collaborate on in projects, hosted in Azure DevOps, though some clients have capability to host or manage source within their infrastructure and prefer that we use their repositories.
It's tempting to just hit the ground running and start coding, but it's important for teams to agree on how to work together as how we work with source control has implications on the degree to which we run in to merge conflicts and other issues that can arise through deferring integration.
Choosing a branching strategy
Modern source control makes branching easier, which is great to temporarily isolate some code from the main branches, but it's important to remember that integrating often (ideally, daily) prevents risk and helps to identify issues early.
Try not to overcomplicate this as complex branching models add overhead to your team and can delay integration, increasing the risk of bugs surfacing late, and making continuous delivery difficult.
Things to value when considering how to branch:
- features can be worked on without delaying deployments of the main branch
- changes can be grouped together and reviewed independently of unrelated changes
- it's possible to continue active development and simultaneously deploy "hotfixes" to production
- keep the main branch always deployable!
Trunk-based development
Most of the software development we do will work best with a simple trunk-based development approach.
Feature development within a trunk-based branching strategy
- Typically when you start a new feature, you should create a new branch off of main. Stick to an agreed upon convention for your feature branches, like feature/new-widget. This makes it easier for others to see what's being worked, and also helps with setting up automated build and test off of specific paths in continuous integration.
- Make changes in your branch and commit frequently! Try to keep your commits as small as possible and keep different types of changes in different commits. Add a useful, but concise commit message. You can push your branch to origin frequently as well if you like, which can be useful if you need to collaborate or just as a backup of your work.
- When you're ready to integrate your feature, open a pull request for your team to review your code and give you feedback.
- Discuss with your teammates, incorporate their feedback, and make your code consistent with the rest of the project.
- Complete the PR and merge your code to main. Congratulations, it's now deployable and can go with the next release. When merging to main, optionally use rebasing to avoid the additional noise of merge commits in history.
Feature flagging and branch-by-abstraction approaches are great ways to ensure that you can integrate code often and keep the main branch always deployable
Hotfixes
Things get only slightly more complicated when we need to deploy an emergency fix to production and you are not able to deploy the current trunk. There are a couple different approaches that mostly differ on when we decide to branch. The following describes a just-in-time branching approach to hotfixes.
- First, fix the bug in main. This keeps main as the source of truth and helps avoids issues like forgetting to merge backwards.
- Find the current commit deployed to production and create a new hotfix branch off of it. Use a naming convention like hotfix/fixes-widget or hotfix/2022-04-11 to keep things consistent.
- Cherry pick your fix commit to the hotfix/* branch
- Your CI/CD pipeline should pick up the new hotfix/* branch for you to promote through environments to production
For an alternate approach that pushes branching earlier, take a look at Release Flow.
Tips:
- Try a simple branching model initially and only add complexity if really necessary. This should be fine for most of the applications we build.
- Separate branches per environment is an anti-pattern. While it might sound like a good idea, it results in testing one build and deploying another. It's a recipe for introducing unintended time dependant or build environment-based changes to prod and issues that can be hard to track down and reproduce.