one restack
Five branches, four worktrees, one command
A week into that work, the stack has drifted in three ways at once:
main gained autosave, so feat/search-index and feat/plugin-api sit on an old trunk commit. Autosave also edits the same save() the search index hooks into.
feat/plugin-api gained a commit, "load plugins from the user's config dir", after feat/search-plugin was built on it. So feat/search-plugin is now on feat/plugin-api~1.
- The fix branch couldn't wait for it, so it cherry-picked its own copy of that commit under its merge. No ref points at the copy, so the only name for its base is a hash.
show reads all of that off the graph. 2e1f9f590a3e is the old trunk commit, and 4e9b8b4d3452 is the copy:
$ git merge-stack show --all
2e1f9f590a3e
├─ feat/search-index 5 commits
│ └─ feat/search-ranking 5 commits
└─ feat/plugin-api 14 commits
└─ feat/search-plugin 11 commits on feat/plugin-api~1 + feat/search-index
4e9b8b4d3452
└─ fix/search-plugin-ranking 1 commit + feat/search-ranking
2e1f9f590a3e > feat/search-index > feat/search-ranking ; 2e1f9f590a3e > feat/plugin-api ; feat/plugin-api~1 + feat/search-index > feat/search-plugin ; 4e9b8b4d3452 + feat/search-ranking > fix/search-plugin-ranking
The fix is to say where each branch should be. Keep the line in a file, one chain per line:
# stack
origin/main > feat/search-index > feat/search-ranking
origin/main > feat/plugin-api + feat/search-index > feat/search-plugin
feat/search-plugin + feat/search-ranking > fix/search-plugin-ranking
Then run it. The search index's save hook conflicts with autosave, so it stops right there, in the worktree it was run from, the way git rebase does:
$ git merge-stack restack -f stack
conflict: feat/search-index: 398a015dfe92 search: update the index when a note is saved: src/app.ts
git-merge-stack: stopped at that commit in ~/src/notes-app; no ref was written yet.
Fix the files there and `git add` them, then:
git merge-stack restack --continue # or --abort to put everything back
$ $EDITOR src/app.ts && git add src/app.ts
$ git merge-stack restack --continue
feat/search-index: 231847e474e7 -> 7f0aba147149
83856947aca1 -> e7c7ab548786 search: tokenize note text
398a015dfe92 -> ce55391f9ff7 search: update the index when a note is saved
64f9d3f5d0e5 -> 2806e9b240e8 search: persist the index next to the notes
b0c5640c1302 -> 9d8e8289cdd9 search: match terms and phrases
231847e474e7 -> 7f0aba147149 search: register the index with the plugin registry
feat/plugin-api: 788b83cc25c9 -> d71f17053cdb
… 14 commits
feat/search-plugin: ef9387f87194 -> 3ab871face44
9d35eef4f1ab -> 2b6a484d5be6 Merge feat/search-index into feat/search-plugin
… 10 more
feat/search-ranking: a95666bd7a39 -> 55534357f0f7
… 5 commits
fix/search-plugin-ranking: 8f1d863bef82 -> 9ecf76054440
8f1d863bef82 -> 9ecf76054440 Merge feat/search-ranking into fix/search-plugin-ranking
dropped, their patch is already in the new history:
fix/search-plugin-ranking: 4e9b8b4d3452 plugin-api: load plugins from the user's config dir
backups: refs/stack-backup/2026-03-14-161205/
All five branches moved, each in its own worktree. The cherry-picked copy was dropped because its patch is already in the new history, through feat/plugin-api. Afterwards show prints exactly the line from the file:
origin/main
├─ feat/search-index 5 commits
│ └─ feat/search-ranking 5 commits
└─ feat/plugin-api 14 commits
└─ feat/search-plugin 11 commits + feat/search-index
└─ fix/search-plugin-ranking 1 commit + feat/search-ranking