Git Stack
Loading tweet…
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.

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:
git switch -c $USERThis is my equivalent ofmainthat I always work on.- Add Feature A
git add && git commit && git push- Add Feature B
git add && git commit && git push- Add
lodashtopackage.json - Use
lodashin Feature B 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 tomain. -
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.
- PR #4: Replace custom utils with
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:
Loading 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:
- c to create a new PR (if you don’t have one already)
- ↑ and ↓ to highlight a commit and Enter to Assign* it to the current PR
- ← and → to navigate between available PRs
- 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:
- Single-branch experience like GitButler (they use
git worktreesto achieve this internally) so I don’t have togit switchorgit stashconstantly. - Easy way to split changes between dependent PRs like git-stack-cli, but still have independent PRs that aren’t stacked like GitButler.
- Do less “book keeping” with AI, and only have human intervention when AI gets it wrong.