git stack
git stack
is a small CLI that makes working with stacked branches in Git easier.
TL;DR โ What can it do?
- ๐
git stack push
โ push a whole stack of branches and open PRs/MRs
- ๐งฑ
git stack branch
โ view branches in your current stack
- ๐
git stack list
โ view all active stacks
- ๐งฐ Integrates with GitHub and GitLab
What is stacking?
Stacking lets you break big changes into smaller, easier-to-review pull requests. Each branch in the stack builds on top of the last. See this intro for a helpful overview.
Why use git stack
?
Git supports stacking out of the box, but it's tedious:
- You have to manually track which branches are stacked together - and in what order
- There's no built-in way to push a full stack of PRs
- Target branches in GitHub/GitLab need to be set by hand
git stack
makes stacking much easier, while aiming to feel like a natural extension of the Git CLI - not a replacement.
Quickstart
You'll need Go 1.22+ installed. On macOS:
brew install go
Install git stack
:
go install github.com/raymondji/git-stack-cli/cmd/git-stack@0.39.0
In your Git repo:
git stack init # Set up with your GitHub/GitLab token
git stack learn # Launch interactive CLI tutorial
Sample usage
This sample output is taken from git stack learn --chapter=1 --mode=exec
.
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Welcome to git stack! โ
โ Here is a quick tutorial on how to use the CLI. โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Let's start things off on the default branch: โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
> git checkout main
Your branch is up to date with 'origin/main'.
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Next, let's create our first branch: โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
> git checkout -b myfirststack
> echo 'hello world' > myfirststack.txt
> git add .
> git commit -m 'hello world'
[myfirststack eb4a6fe] hello world
1 file changed, 1 insertion(+)
create mode 100644 myfirststack.txt
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Now let's stack a second branch on top of our โ
โ first: โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
> git checkout -b myfirststack-pt2
> echo 'have a break' >> myfirststack.txt
> git commit -am 'break'
[myfirststack-pt2 e3579cb] break
1 file changed, 1 insertion(+)
> echo 'have a kitkat' >> myfirststack.txt
> git commit -am 'kitkat'
[myfirststack-pt2 7c3f014] kitkat
1 file changed, 1 insertion(+)
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ So far we've only used standard Git commands. โ
โ Let's see what git stack can do for us already. โ
โ โ
โ Our current stack has two branches in it, which โ
โ we can see with: โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
> git stack branch
* myfirststack-pt2 (top)
myfirststack
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Our current stack has 3 commits in it, which we โ
โ can see with: โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
> git stack log
7c3f014 kitkat
e3579cb break
eb4a6fe hello world
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ We can easily push all branches in the stack and โ
โ open PRs. โ
โ git stack automatically sets the target branches โ
โ for you. โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
> git stack push --open
[?25l[?2004h
โฃฝ Pushing stack...[Dโฃป Pushing stack...[Dโขฟ Pushing stack...[Dโกฟ Pushing stack...[Dโฃ Pushing stack...[Dโฃฏ Pushing stack...[Dโฃท Pushing stack...[Dโฃพ Pushing stack...[Dโฃฝ Pushing stack...[Dโฃป Pushing stack...[Dโขฟ Pushing stack...[Dโกฟ Pushing stack...[Dโฃ Pushing stack...[Dโฃฏ Pushing stack...[Dโฃท Pushing stack...[Dโฃพ Pushing stack...[Dโฃฝ Pushing stack...[Dโฃป Pushing stack...[Dโขฟ Pushing stack...[Dโกฟ Pushing stack...[Dโฃ Pushing stack...[Dโฃฏ Pushing stack...[Dโฃท Pushing stack...[Dโฃพ Pushing stack...[Dโฃฝ Pushing stack...[Dโฃป Pushing stack...[Dโขฟ Pushing stack...[Dโกฟ Pushing stack...[Dโฃ Pushing stack...[Dโฃฏ Pushing stack...[Dโฃท Pushing stack...[Dโฃพ Pushing stack...[Dโฃฝ Pushing stack...[Dโฃป Pushing stack...[Dโขฟ Pushing stack...[Dโกฟ Pushing stack...[Dโฃ Pushing stack...[Dโฃฏ Pushing stack...[Dโฃท Pushing stack...[Dโฃพ Pushing stack...[Dโฃฝ Pushing stack...[Dโฃป Pushing stack...[D[2K
[?2004l[?25h[?1002l[?1003l[?1006lPushed branches:
* myfirststack-pt2 (top)
โโโ https://github.com/raymondji/git-stack-cli/pull/209
myfirststack
โโโ https://github.com/raymondji/git-stack-cli/pull/208
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ We can quickly view the PRs at any point using: โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
> git stack branch --prs
[?25l[?2004h
โฃฝ Fetching prs...[Dโฃป Fetching prs...[Dโขฟ Fetching prs...[Dโกฟ Fetching prs...[Dโฃ Fetching prs...[D[2K
[?2004l[?25h[?1002l[?1003l[?1006l* myfirststack-pt2 (top)
โโโ https://github.com/raymondji/git-stack-cli/pull/209
myfirststack
โโโ https://github.com/raymondji/git-stack-cli/pull/208
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ To sync the latest changes from the default โ
โ branch into the stack, you can run: โ
โ git rebase main --update-refs โ
โ Or to avoid having to remember --update-refs, โ
โ you can do: โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
> git stack rebase main
Successfully rebased myfirststack-pt2 on main
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Great, we've got the basics down for one stack. โ
โ How do we deal with multiple stacks? โ
โ Let's head back to our default branch and create โ
โ a second stack. โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
> git checkout main
Your branch is up to date with 'origin/main'.
> git checkout -b mysecondstack
> echo 'buy one get one free' > mysecondstack.txt
> git add .
> git commit -m 'My second stack'
[mysecondstack dd16f86] My second stack
1 file changed, 1 insertion(+)
create mode 100644 mysecondstack.txt
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ To view all the stacks: โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
> git stack list
myfirststack-pt2 (2 branches)
* mysecondstack (1 branch)
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ โ
โ Nice! All done chapter 1 of the tutorial. โ
โ โ
โ In chapter 2 we'll see how to make changes to โ
โ earlier branches in the stack. โ
โ Once you're ready, continue the tutorial using: โ
โ git stack learn --chapter 2 โ
โ โ
โ To cleanup all the branches/PRs that were โ
โ created, run: โ
โ git stack learn --chapter 1 --mode=clean โ
โ โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
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
.