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 frommain
- Make your relevant commits
- Create your PR from
feature-1
to the defaultmain
- While remaining on
feature-1
, create a new branchfeature-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
andfeature-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.
- If you accidentally created the PR to
There are a few important notes / caveats to using this method
- When updating
feature-2
, you should always pull changes in throughfeature-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 frommain
, you must pull them intofeature-1
first, and then pullfeature-1
intofeature-2
. This can often causefeature-2
to become stale as people forget about updatingfeature-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 forfeature-2
to point back tomain
. If this step is overlooked (and it often is), you’ll merge your code intofeature-1
thinking that it’s done, but it will never get released. - Merge
feature-2
intofeature-1
first, and then merge the combined code that’s now infeature-1
intomain
. This requires thatfeature-1
be held up from releasing when it’s done, so it should be avoided when possible.
- Merge in
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 frommain
- 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 tobig-feature
instead ofmain
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 yourchild-2
branch.- Note:
child-2
will not have your changes fromchild-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.
- Note:
- Commit your changes and create a PR for
child-2
ensuring that you also change the base branch tobig-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
tomain
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).