README
¶
ghenga
A command-line tool for managing stacked pull requests with git.
A tower is a sequence of dependent branches where each one builds on the previous. No more juggling rebases by hand when a branch below changes. Ghenga lets you rebase the whole stack in one shot, push every branch to the remote in order, and land branches one at a time from the bottom up.
Install
go install github.com/cpoile/ghenga@latest
Commands
ghenga init— register the current repository in ghenga's configghenga new <tower>— create a new tower and make it the current oneghenga ls— list towers and their branches (this is the default command)ghenga current <tower>— switch to a different towerghenga rename <new-name>— rename the current towerghenga base <branch>— set the current tower's base branch (e.g.main)ghenga base <branch> --strategy=merge— set the base and switch the tower to the merge strategy (see Two strategies);--strategy=rebaseis the default
ghenga strategy <merge|rebase>— set the current tower's strategy without changing its base branchghenga add <branch>— add an existing branch to the current towerghenga branch-rm <branch>— remove a branch from the current tower (the git branch itself is untouched)ghenga tower-rm <tower>— remove a towerghenga rebase— rebase branches that have diverged from the branch below themghenga rebase onto <ref>— rebase the whole tower onto a new baseghenga rebase from <branch> <commit>— partial rebase starting at<commit>on<branch>ghenga rebase continue— resume after resolving conflictsghenga rebase cancel— abort an in-progress rebaseghenga rebase undo— revert the last completed rebase
ghenga merge— the merge-strategy counterpart ofrebase: merge each branch's base down into it for branches that have divergedghenga merge onto <ref>— merge a new base down through the whole towerghenga merge from <branch>— merge the base down starting at<branch>ghenga merge continue— resume after resolving conflictsghenga merge cancel— abort an in-progress mergeghenga merge undo— revert the last completed merge
ghenga sync— push every branch in the tower to the remote (force-with-lease where needed)ghenga sync undo— undo the last sync
ghenga land— land the bottom branch: pull its merge into the base, drop it from the tower, and rebase the rest (or, on a merge-strategy tower, merge the new base down into the rest)ghenga checkpoint [name]— save a snapshot of every branch's position (and the tower's membership and base);namedefaults to a timestampghenga restore— list the saved checkpoints and roll the tower back to the one you pickghenga config— print the path to the ghenga config fileghenga completion— emit shell completions
How a tower relates to main
In plain git, a stack of pull requests is a chain of branches, each pointing at the tip of the one below:
o---o---o main
\
o---o auth-models
\
o---o auth-handlers
\
o---o auth-ui
The PR for auth-models targets main; auth-handlers targets
auth-models; auth-ui targets auth-handlers. That's the "stack."
ghenga records this as a tower:
3. auth-ui PR → auth-handlers
2. auth-handlers PR → auth-models
1. auth-models PR → main
Tower: auth (base: main)
A typical flow
Say you're building an auth feature in three reviewable pieces. Each branch ends up with two commits.
1. Initialize and create the tower.
ghenga init
ghenga new auth
2. Build the first branch and add it.
git checkout -b auth-models
# ... work, commit, work, commit ...
ghenga add auth-models # first add prompts you to pick a base (main)
3. Stack the next branch on top.
git checkout -b auth-handlers # branches off auth-models' current tip
# ... two commits ...
ghenga add auth-handlers
4. And the third.
git checkout -b auth-ui
# ... two commits ...
ghenga add auth-ui
After those three adds, the repo looks like:
o---o---o main
\
o---o auth-models
\
o---o auth-handlers
\
o---o auth-ui
5. Push the stack and open PRs.
ghenga sync
sync pushes every branch to the remote in order, force-pushing (with lease)
any that have been rewritten.
Fixing a review comment on the bottom PR
A reviewer leaves feedback on the auth-models PR. You check it out and
commit a fix:
git checkout auth-models
# ... edit, test ...
git commit -am "Address review feedback"
auth-models now has a new commit, but auth-handlers and auth-ui still
point at the old tip. The stack is out of sync:
o---o---o main
\
o---o---* auth-models (* = the new review-fix commit)
\
o---o auth-handlers (still branched from the OLD tip)
\
o---o auth-ui
Rewire the stack:
ghenga rebase
ghenga sees that auth-handlers has diverged from auth-models,
cherry-picks its commits onto the new auth-models tip, then does the same
for auth-ui onto the rebased auth-handlers:
o---o---o main
\
o---o---* auth-models
\
o'--o' auth-handlers (rewritten)
\
o'--o' auth-ui (rewritten)
If a cherry-pick hits a conflict, ghenga pauses. Resolve the conflict,
git add the files, then run ghenga rebase continue to pick up where it
left off. (ghenga rebase cancel aborts; ghenga rebase undo reverts a
completed rebase.)
Now push the rewritten branches:
ghenga sync
Landing the bottom PR
Once the bottom PR is approved and merged on the remote (and the remote branch deleted — typically automatic after a squash-merge), run:
ghenga land
ghenga will:
- Fetch the remote and fast-forward the local
main. - Drop
auth-modelsfrom the tower's branch list (the local git branch is left alone). - Rebase
auth-handlersonto the newmain, excluding the commits that already landed via the squash-merge. - Rebase
auth-uionto the rebasedauth-handlers.
Before land:
o---o---o---M main (M = the squash-merge, fetched from remote)
\
o---o---* auth-models (merged; removed from the tower)
\
o---o auth-handlers
\
o---o auth-ui
After land:
o---o---o---M main
\
o'--o' auth-handlers (rebased onto new main)
\
o'--o' auth-ui
The tower is now a two-branch stack with auth-handlers as the new bottom
PR. Push the rewritten branches with ghenga sync, and repeat when that PR
lands.
Two strategies: rebase vs merge
Everything above uses the default rebase strategy: when a lower branch changes, ghenga rewrites the branches above it so the stack stays linear. Clean history — but rewriting means new commit hashes and a force-push, and GitHub can't map your reviewers' existing comments and "viewed" checkboxes onto the new commits. On a tall stack, every fix to the bottom PR resets review state on the ones above it.
The merge strategy trades linear history for review continuity. Instead of rebasing the upper branches up onto a moving base, it merges the base down into each branch — adding a merge commit on top while leaving every existing commit untouched. Because the old commits keep their hashes, reviewers keep their state on the higher PRs, and because each merge brings the base into the branch's history, the branch stays cleanly mergeable.
This is designed for teams that squash-merge PRs on GitHub. The merge
commits only ever live on the PR branches; squash-merge flattens each PR to a
single commit on main, so the merge bubbles never reach your main history.
Pick one strategy per tower and stick with it — mixing rewritten and merged history in the same stack gets confusing.
Switching a tower to the merge strategy
ghenga base main --strategy=merge
This sets the base and the strategy together. Changing the strategy later warns
you and asks for confirmation (and refuses while a rebase/merge is paused
mid-conflict). On a merge-strategy tower, ghenga rebase will stop and point
you at ghenga merge (and vice-versa) so you never accidentally rewrite a
stack you meant to keep stable.
Fixing a review comment (merge strategy)
Same situation as before — a reviewer leaves feedback on auth-models, you
commit a fix, and the stack falls out of sync:
git checkout auth-models
git commit -am "Address review feedback"
Instead of ghenga rebase, run:
ghenga merge
ghenga merges auth-models down into auth-handlers, then the updated
auth-handlers down into auth-ui:
o---o---o main
\
o---o---* auth-models (* = the review-fix commit)
\ \
o---o---M auth-handlers (M = merge commit; original
\ \ commits unchanged)
o---o---M' auth-ui
Note what didn't happen: auth-handlers and auth-ui kept all their
original commits. Their PRs gain a merge commit but lose no review state. Push
with ghenga sync — these are fast-forward pushes, no force needed.
If a merge hits a conflict, ghenga pauses. Resolve it, git add the files, then
run ghenga merge continue (or ghenga merge cancel to abort, or
ghenga merge undo to revert a completed merge).
Landing the bottom PR (merge strategy)
Once auth-models is squash-merged and its remote branch deleted, run
ghenga land exactly as before. On a merge-strategy tower it:
- Fetches the remote and fast-forwards local
main(now containing the squash-merge commitM). - Drops
auth-modelsfrom the tower. - Merges the updated
maindown intoauth-handlers, thenauth-handlersintoauth-ui— no rewriting.
o---o---o---M main (M = the squash-merge of auth-models)
\ \
o---o---M auth-handlers (merge of main; own commits intact)
\ \
o---o---M' auth-ui
Because main's squash commit is now in auth-handlers' history, the
auth-handlers PR cleanly shows only its own diff — ready for its own
squash-merge when its turn comes. Push with ghenga sync and repeat.
Checkpoints
rebase, merge, and land all have a one-step undo, but sometimes you want
a named safety net before a risky operation — or several restore points to
choose from. Checkpoints record where every branch in the tower points, plus
the tower's branch list and base, so you can roll the whole stack back later.
Take a snapshot before doing something hairy:
ghenga checkpoint before-big-rebase
The name is optional — leave it off and ghenga names the checkpoint with the current date and time. Checkpointing only reads branch positions, so it's safe to run with a dirty working tree, and you can keep as many as you like. Re-using a name asks before it overwrites the old snapshot.
Later, roll back with:
ghenga restore
restore lists the tower's checkpoints, newest first, and asks which one to use:
Checkpoints for tower 'auth':
1. before-big-rebase (2026-06-26 14:02:11) 3 branches [latest]
2. 2026-06-25T09-11-03 (2026-06-25 09:11:03) 3 branches
Pick a checkpoint to restore [1]:
Press enter to take the latest, or pick a number. After you confirm, ghenga:
- Moves each recorded branch back to its saved commit (recreating the branch if it has since been deleted).
- Restores the tower's branch list and base to what they were at checkpoint time — so a branch that was landed and dropped from the tower in the meantime is added back.
Restore rewrites branch refs, so it needs a clean working tree and won't run
while a rebase or merge is paused mid-conflict. If any branch is checked out in
a worktree, ghenga offers to reset that worktree to the restored commit. Once
you've restored, push the branches with ghenga sync.
Documentation
¶
There is no documentation for this package.