package
0.0.0-20250119214544-7fd3cedad9d9
Repository: https://github.com/raymondji/git-stack-cli.git
Documentation: pkg.go.dev

# README

git stack

A minimal CLI that makes native stacked branches more ergonomic. Integrates with Gitlab and Github.

Core usage:

  • git checkout -b myfeature: create branches how you normally would
  • git stack branch: list branches in the current stack, in order
  • git stack push: push branches in the current stack and open MRs/PRs
  • git stack list: list all stacks

What is stacking?

https://graphite.dev/blog/stacked-prs has a good overview on what it is and why you might want to do it.

What's hard about stacking branches in Git?

Stacking branches is natively supported in Git, and has been made better with recent additions like --update-refs. If you stack infrequently, I think the Git CLI provides a good enough out-of-the-box experience.

However, if you stack frequently (or would like to, e.g. to create small PRs), I think the out-of-the-box experience falls short:

  • Keeping track of which branches are stacked together, and in which order, is left to the user. If you modify your branches into some degenerate stack, it's also on you to figure out there's a problem.
  • It's not clear how to push all branches in a stack except listing them out individually.
  • Once you've pushed your branches, you also need to manually set the target branches on Gitlab/Github. If you want to give reviewers context about other PRs in the stack, that's manual too.

One way to improve things is to adopt a lot of custom git aliases and shell scripts. I did that for a long time, but some things were still hard to automate robustly.

Why git stack?

git stack aims to provide a small but useful feature set that makes working with native stacked branches easier.

Many of the popular stacking tools require external metadata to keep track of stacks. They work really well and can use that external state to drive more powerful features, but as a user you can't just git checkout -b myfeature anymore. git stack makes different tradeoffs and works entirely on top of native Git. It's stateless, and works by automatically parsing stacks from your commit structure. It works most similarly to https://github.com/gitext-rs/git-stack.

TODO: Other options model stacks using one branch per stack and one commit per pull request. This approach (inspired by Phabricator) can work really well with rebase workflows. However, this approach feels less native to Git, and trades off some of the flexibility of Git. git stack works entirely on top of native Git features, so you can use one commit per branch/PR, or multiple commits; and you can use git rebase or git merge to manage your stacked branches.

Lastly, git stack helps with the other half of the puzzle. It integrates with both Gitlab and Github to automate creating and updating MRs/PRs from a stack. I was surprised to find that most of the popular stacking tools only support Github.

Installation

Go version >= 1.22 is required. To install Go on macOS:

brew install go 

To install git stack:

go install github.com/raymondji/git-stack-cli/cmd/git-stack@{{ .Version }}

Getting started

The git stack binary is named git-stack. Git offers a handy trick allowing binaries named git-<foo> to be invoked as git subcommands, so git stack can be invoked as git stack.

git stack needs a Gitlab/Github personal access token in order to manage MRs/PRs for you. To set this up:

cd ~/your/git/repo
git stack init

To learn how to use git stack, you can access an interactive tutorial built-in to the CLI:

git stack learn

Sample usage

This sample output is taken from git stack learn --chapter=1 --mode=exec.

{{ .SampleOutput }}

How does it work?

When working with Git we often think in terms of branches as the unit of work, and Gitlab/Github both tie pull requests to branches. Thus, git stack presents stacks as "stacks of branches".

However, branches in Git don't inherently make sense as belonging to a "stack", i.e. where one branch is stacked on top of another branch. Branches in Git are just pointers to commits, so:

  • Multiple branches can point to the same commit
  • Branches don't inherently have a notion of parent branches or child branches

Under the hood, git stack therefore walks the commit graph and parses stacking relationships between branches. Commits serve this purpose well because:

  • Each commit is a unique entity
  • Commits do inherently have a notion of parent commits and child commits

git stack uses the commit relationships to try and establish a total order between branches in a stack, i.e. where each branch i contains branch i-1. If such an order exists, the stack is valid. If such an order doesn't exist, the stack is invalid and git stack prints a helpful error message so you can resolve the bad state.

Attribution

Some code is adapted from sections of https://github.com/aviator-co/av (MIT license). A copy of av's license is included at attribution/aviator-co/av/LICENSE.