Git Stack

I’ve been a big fan of GitButler for a few years now – it let’s me work on multiple changes at once, and then split them up into independent PRs.

GitButler Screenshot

What I’ve found lately is that there’s been more ceremony (read: friction) with splitting up my changes across lanes.

What I wanted was a way to work in 1 branch, but break up logical chuncks into PRs that can be reviewed & land independently.

Let AI do it

AI is great with nuance, non-deterministic decisions based on context. The workflow I wanted was:

  1. git switch -c $USER This is my equivalent of main that I always work on.
  2. Add Feature A
  3. git add && git commit && git push
  4. Add Feature B
  5. git add && git commit && git push
  6. Add lodash to package.json
  7. Use lodash in Feature B
  8. git add && git commit && git push

In GitHub, I wanted to see:

  • PR #1: ☂️ eric This is an “umbrella” PR that can be used to review all changes in one place. At the start of the week, there’ll often be no changes. At the end of the week, there’ll be a lot of changes here but broken up into multiple PRs.

  • PR #2: Feature A (pr-split/eric/feature-a) This PR is automatically created by AI based on changes in the ☂️ that AI determines can be landed independently to main.

  • PR #3: Feature B (pr-split/eric/feature-b) Similarly, AI creates a separate PR that’s logically different from Feature A.

    • PR #4: Replace custom utils with lodash (pr-split/eric/feature-b/refactor-lodash) AI creates a “stack” for this refactor that depends on Feature B, but can be reviewed independently.

Claude Code cranked this out with fairly good success:

The secret sauce is using Git Trailers for metadata that survives rebasing. When rebasing the $USER branch against main, the commit hashes change! But I needed a deterministic way the original commit to avoid duplicative work. (Alternatively, I think the git diff would be a better hash since those are the contents that AI is considering!)

I was able to make it work, but there would be a lot of tweaking to make it work well:

  • Optimized to use increasingly more powerful models based on the complexity of the changes
  • Optimize for incremental commits
  • Only generate PRs when the ☂️ PR is “Ready for Review”

What was nice is that AI could also listen to a PR Review Comment and adjust the PRs accordingly!

/pr-splitter This change should be on #3, not #4

Or, if I wanted to re-run the script for whatever reason, I could manually trigger the workflow, or just comment:

🔀

git-stack-cli

And this is why I tweet:

Installation is a simple:

brew install magus/git-stack/git-stack

Then, I had to make sure that my origin references were correct. Otherwise, I had a stale reference to origin/main that had HUNDREDS of invalid commits!

git remote prune origin

Finally, I was ready for my first stack:

$ git stack

 NEW   0/26   Unassigned


👋 Welcome to git stack!
Press c to (c)reate a new PR

 Prettier
 Move opencode workaround to post-commit hook
 Add test file 15
 Refactor prepare-commit-msg hook for streamlined commit generation
 Add test-file14.txt
 Test 13
 Only test-file12.txt is staged
 Manual message for test-file11.
 Add test-file8.txt
 Add test-file7.txt
 Add test-file6.txt
 Manual test-file4 commit to see if opencode is doing `git add .`
 chore: test commit
 Add test-file2.txt
 Add test file
 Update opencode permissions in prepare-commit-msg hook
 Add rule against tool calls and git operations in prepare-commit-msg
 Draft git-stack
 Remove PR Splitter feature
 Remove Husky pre-commit hook
 Update opencode permission defaults to boolean false
 Add prepare-commit-msg section to auto-commit blog post
 Update opencode permission key from edit to write
 Add blog post on auto-commit messages with OpenCode
 Integrate opencode for automatic commit message generation
 Add Flox blog post

26 unassigned commits
Press c to (c)reate a new PR
Press and to view PRs
Press Enter to toggle commit selection

After c, Add Flox blog post was automatically prefilled as a PR title.

s then synced this to GitHub as Add Flox blog post:

 NEW   0/26   Unassigned

Created new PR [Add Flox blog post] (eric---4h5sg2pldxtz8-)

 NEW      0/25   Unassigned
 SYNCED   1/1    Add Flox blog post   https://github.com/ericclemmons/ericclemmons/pull/99

Now that I’ve figured out the flow for one of them, I learned that the workflow is basically:

  1. c to create a new PR (if you don’t have one already)
  2. and to highlight a commit and Enter to Assign* it to the current PR
  3. and to navigate between available PRs
  4. Finally, s to sync the PRs to GitHub:
$ git stack
📦 Changes saved to stash

 NEW      0/25   Unassigned
 SYNCED   1/1    Add Flox blog post   https://github.com/ericclemmons/ericclemmons/pull/99

Created new PR [Test Commits] (eric---4h5shgjoil6oxc)
Created new PR [Automatic git commit messages] (eric---4h5shr5o793bv8)
Created new PR [Draft git-stack] (eric---4h5si6up1575bl)
Created new PR [Remove PR Splitter feature] (eric---4h5si9vt4e_lz_)

 NEW      0/1     Unassigned
 SYNCED   12/12   Test Commits                    https://github.com/ericclemmons/ericclemmons/pull/100
 SYNCED   1/1     Draft git-stack                 https://github.com/ericclemmons/ericclemmons/pull/101
 SYNCED   2/2     Remove PR Splitter feature      https://github.com/ericclemmons/ericclemmons/pull/103
 SYNCED   9/9     Automatic git commit messages   https://github.com/ericclemmons/ericclemmons/pull/102
 SYNCED   1/1     Add Flox blog post              https://github.com/ericclemmons/ericclemmons/pull/99
 Everything up to date.
 Changes restored from stash

Now, I realize my mistake – this works best for a stack of dependent PRs, not for independent PRs like I wanted with my AI experiment.

I could be wrong, but this seems best when you need to split a large PR into smaller changes for review.

Conclusion

I want the best of both three worlds:

  1. Single-branch experience like GitButler (they use git worktrees to achieve this internally) so I don’t have to git switch or git stash constantly.
  2. Easy way to split changes between dependent PRs like git-stack-cli, but still have independent PRs that aren’t stacked like GitButler.
  3. Do less “book keeping” with AI, and only have human intervention when AI gets it wrong.