Git is a very powerful versioning system and there is tons of information out there for more advanced scenarios.  I wrote this up for one of my teams that was new to both Git and the release-when-ready model so they were running into overlapping branches fairly often.  None of these patterns are set in stone and personal judgement should be used in all cases.

General branch usage

Branches should be thought of as a collection of commits. They should be treated as disposable, created and deleted at will.

Creating a branch

For a typical feature being developed, you should start from the main branch, ensure it’s up-to-date via git pull or git fetch, and then create your new branch (using any team naming conventions).

Changing the name of a branch

If you mistype or want to change the name of your branch, you can simply just create a new branch from wherever you are in development. The key is the commits themselves and how they differ from the target/destination branch (main by default).

Undoing commits

If you commit something and then decide you want to undo those changes, you can revert that specific commit, create a new branch and cherry-pick the previous commits, or for more advanced users, rewriting commits. Once a Pull Request is opened though, rewriting commits is bad practice as reviewers aren’t able to look at your specific changes to their feedback.

Handling dependencies

If you need the code from an in-flight branch for a new feature you’re starting, there are several ways to handle it.

Combine stories into a single branch

If you’re able to see that multiple prioritized stories are going to be touching similar areas of code, you may want to consider proposing that the stories be combined so that you can develop both in the same branch.

This method should be reserved for only small changes with similar levels of priority since we still want to avoid larger changes as they’re harder to review, test, monitor, and triage. If the stories are not close in priority, it’s best to keep them separate.

Wait for the dependent code to be merged (preferred)

If you have changes complete and already through review but you need those changes to begin new work, you should work to shepherd your PR through the rest of the process quickly, get the changes merged, and then you can start cleanly from main.

You may have to pair up with your code reviewer(s) and SDET(s) to walk through the code and test scenarios to speed up those parts of the process.

This method is preferred as it keep changes smaller, reduces overhead, and helps reduce our team’s cycle time (time between PR created and PR merged). If using this method causes a delay in the next feature you’re working on, please ensure the PM knows this and it’s not an issue.

Create a daisy chained branch

Since a branch is just a pointer to a commit or series of commits, you can create an endless number of branches on top of each other, and then when creating a PR, you can change the ‘base’ branch to a different upstream branch (other than main).

For example:

  • Create feature-1 branch from main
  • Make your relevant commits
  • Create your PR from feature-1 to the default main
  • While remaining on feature-1, create a new branch feature-2
  • Create new relevant commits
  • Push your branch to github and start creating your PR
  • Notice how, by default, all commits are showing up from feature-1 and feature-2
  • Before creating the PR, change the ‘base’ branch to feature-1 and the commit list will only show the new commits created after you created the first PR.
    • If you accidentally created the PR to main you can click on ‘Edit’ and change the ‘base’ branch to the upstream branch and the commits / code changes will be updated accordingly.

There are a few important notes / caveats to using this method

  • When updating feature-2, you should always pull changes in through feature-1. When using the github UI to perform an update, it should automatically pull from whatever base branch you are targeting, but you must be extra careful when updating via command line or your editor. If you want/need any changes from main, you must pull them into feature-1 first, and then pull feature-1 into feature-2. This can often cause feature-2 to become stale as people forget about updating feature-1 regularly since they’ve moved onto developing the next feature.
  • When merging, you have to take one of two routes
    • Merge in feature-1 when it’s done being tested as usual, then you must update the PR for feature-2 to point back to main. If this step is overlooked (and it often is), you’ll merge your code into feature-1 thinking that it’s done, but it will never get released.
    • Merge feature-2 into feature-1 first, and then merge the combined code that’s now in feature-1 into main. This requires that feature-1 be held up from releasing when it’s done, so it should be avoided when possible.

Because of the complexity and overhead of this scenario, we should never daisy chain more than one level of branches/PRs.

Utilize a parent branch for both changes

Sometimes you have a large feature that has several moving pieces and you’ll want to split up the work by tackling different parts in different branches and they wouldn’t add any value when releasing by themselves. In those cases, you can use this strategy to compartmentalize the work, reviews, and testing without releasing between each step.

Depending on the changes, sometimes testing can occur only on the parent branch after all child branches are merged rather than test on each child branch.

For example:

  • Create big-feature branch from main
  • Do not make any commits directly into big-feature
  • Immediately create the first child branch for the first set of changes, child-1
  • Commit your changes and create a PR for child-1 ensuring that the base branch in the PR is pointed to big-feature instead of main
    • big-feature is still empty at this point
  • Start and finish the code reviews and testing on that individual set of changes
  • Switch back to big-feature and create your child-2 branch.
    • Note: child-2 will not have your changes from child-1 so the changes must be completely separate. If you need the code from the first branch, you should not be using this strategy, and instead use the daisy chaining strategy above. While you technically can combine these strategies, it gets confusing quickly and should be avoided.
  • Commit your changes and create a PR for child-2 ensuring that you also change the base branch to big-feature
  • Now both changes are in flight independently, but if either of them get merged, they will not trigger a release to staging.
  • When you’re ready, you can create a PR for big-feature to main that will contain all of the child PRs after they’re merged. If you create this PR before the children are merged, please add a ‘blocked’ label so it doesn’t get merged prematurely.

Similar to the daisy chain strategy, if you want to get changes from main into your feature branch, you must do it through the big-feature branch (update big-feature from main, then update child-1/2 from big-feature).