GitButler Logo
Features

Stacked Branches

Create a stack of dependent branches to be reviewed and merged in order.

Overview

GitButler allows you to create an ordered stack of branches where each branch depends on (and is based on) the previous one. The application also supports creating the appropriate stacked Pull Requests (when used with a GitHub remote). This is useful when you have multiple changesets that depend on each other but it is desirable to have them reviewed and merged separately (and in sequence).

All of the Pull Request stack orchestration is done locally in the client, which means that your repo content is not shared with a cloud service.

Use cases

Using stacked branches (Pull Requests) can be helpful for shipping smaller changes more frequently.

Breaking up a larger change into smaller ones

Consider a scenario where you are implementing a medium/large feature in your software project. In the course of implementation you end up performing the following sub-tasks:

  1. Refactor a part of the codebase to accommodate the new feature
  2. Implement an API endpoint supporting the feature
  3. Implement the frontend part of the feature consuming the API

While the feature is considered complete only when all of the subtasks are implemented, reviewed and merged, in many cases it is considered beneficial to ship each stage of the feature on its own, potentially behind a feature flag. Not only the risk of merge conflicts with colleagues is reduced, but also eventual bugs are easier to track down / revert / fix as compared to a single large change.

More granular (easier) review process

On GitHub at least, code reviews are performed on per-branch basis. While it is possible to view individual commits in a Pull Request, it is not possible to approve and merge a subset of commits from the PR.

Utilizing stacked pull requests, means that the sub-tasks of a larger change are in their own PRs. This way it is possible to approve and merge the initial part of a stack (e.g. a refactor) while still iterating on the remaining sub-tasks.

Comparison to Virtual Branches

Stacking and Virtual Branches are similar in that they allow you to separate code changes / commits into different branches. In both cases, the changes are available in your working directory.

The main difference is that Virtual Branches are independent from one another, while stacked branches depend on the ones that come before it. Because of this, the two features are not mutually exclusive but rather complementary. For example a bugfix change that is unrelated to a feature can be put in a separate virtual branch. On the other hand, a change that depends on a previous change can be put in a stacked branch above the one it depends on.

In fact GitButler implements stacked branches as Virtual Branches that are split into multiple dependent branches.

Workflow

By default, virtual branches in the app are simply stacks of one. With version 0.14.0 or newer you can create a new dependent branch within a lane by clicking the + button above the branch name.

The workflow below assumes a GitHub remote. If you are using a different forge, you can still use this functionality but will need to manually create/update the Pull/Merge Requests

  1. Creating a new dependent branch forms a stack within the lane.
  1. New commits land in the top branch of the stack.
  1. Pushing is done for the stack as a whole. Note: The Pull Requests will be created in a way where each branch points to its parent - see Automatic branch deletion
  1. Pull requests must be created one at a time starting from the bottom of the stack.
  1. The PRs will contain a footer with stack information, and as you add more PRs it will keep all up to date.
  1. You can drag changes into commits to amend them (e.g. incorporating review feedback) as well as move and squash commits.
Amending a commit
Amending a commit
Moving a commit to a different branch
Moving a commit to a different branch
Squashing commits
Squashing commits
  1. If a change in your stack is independent (e.g. an unrelated bugfix) it can be moved to a different virtual branch (or stack). This works for both uncommitted changes and existing commits that you may want to relocate.
  1. Review/merge your PRs starting from the bottom up. After a PR/branch from your stack has been merged, it is reflected in the Stack and you should force push to reflect the changes on the remote as well.
  1. When all branches of a stack have been merged, the stack is complete.

GitHub configuration for stacked PRs

TLDR:

  1. Enable automatic branch deletion automatic branch deletion on GitHub.
  2. If possible, consider using the the "Merge" strategy when merging PRs.

Automatic branch deletion

When reviewing a PR in a stack, it is important to be able to view only the changes in the branch that is being reviewed. Of course, in pure Git terms, a stacked branch will contain all the changes from the branches below it.

In order to show only the expected Files changed and Commits for PRs in a stack, each PR is created to target the branch below it in the stack. This is true for all but the bottom branch in the stack, which targets the default branch of the repository as usual.

Every branch in the stack contains the commits from the branches below it.

This of course does not mean that a Pull Request should be merged into its parent. When the bottom branch is merged on GitHub, if the PR branch is deleted, GitHub will automatically update any PRs that used to target it to target the default branch instead.

If the newly merged branch from the bottom of the stack is not deleted, the next PR in line will still target it and there is a risk of accidentally merging it into the now out of data branch. For this reason it is highly recommended to enable on GitHub the automatic deletion of branches after merging.

NB: If you merge the first PR but the branch is not deleted and then merge the second PR, the app can still recover from this, see Troubleshooting.

Merge strategy

The app will support any merge strategy you wish to use - "Merge", "Rebase" or "Squash". However, due to the nature of merge, the GitButler will be able to create a slightly better experience if the "Merge" strategy is used. The reason for this is with merge commits you will be able to merge all the branches in the stack from GitHub without having to force push in the app.

Troubleshooting

Firstly, if you run into any issue with the app (stacking or not), you can always get in touch either on Discord or via the in-app feedback icon (we will get back via email). With that said, here are some workarounds for common issues.

Accidentally merged a stack branch into an already merged branch before it

If you merged the bottom Pull Request into main but the branch was not deleted, then the target of the next Pull Request would not be automatically updated. Under these conditions merging that next Pull Request, means it would be merged into the original, now out of date, bottom PR.

A mitigation for this is to rename the branch, push and re-create the Pull Request.

Accidentally merged a branch into a branch before it (not integrated into main/master yet)

Merging of branches in a stack should be done from the bottom up. With the GitHub interface, it is possible to incorrectly merge a Pull Request which is in the middle of the stack. In this case it will merged in the parent branch.

In order to recover from this situation you can simply force push the branches and then re-create the PR that was incorrectly merged.

Last updated on

On this page

Edit on GitHubGive us feedback