Absorb fixups
You're three branches deep in a stack and you spot a typo (or a missed null-check) in commit 4. You want the fix to land in commit 4, not as a fresh commit on the current tip.
What you'd otherwise do
The manual flow is a three-step template (substitute <sha-of-commit-4> for the actual ancestor SHA you want the fixup to land on):
git commit --fixup=<sha-of-commit-4>
git rebase -i --autosquash main
sm restackThree commands, the middle one drops you into an interactive editor. Repeat per hunk if multiple commits are involved.
With sm absorb
sm absorb wraps git-absorb, which matches each hunk to the commit that introduced the surrounding code:
sm absorbWhat happens:
git-absorblooks at every hunk in the working tree.- For each hunk, it
git blames the surrounding lines to find the right ancestor commit. - It creates
fixup!commits and runsgit rebase --autosquashto fold them in. smrestacks every descendant so they pick up the new commits.
End result: one command, no editor, descendants are consistent.
Prerequisites
Install git-absorb from one of the sources below.
On macOS via Homebrew:
brew install git-absorbOn any platform with rustup / cargo:
cargo install git-absorbIf git-absorb is missing, sm absorb prints an install hint and exits. It's not bundled with sm.
Worked example
Starting state:
stac-man
main
└─ feat/api-models
└─ feat/api-handlers
└─ feat/api-tests ← current(Working tree on feat/api-tests is dirty with a fix that conceptually belongs in feat/api-handlers.)
The dirty hunk lives in internal/handlers/login.go — code that was originally written in feat/api-handlers's second commit. Run:
sm absorbOutput:
✓ absorbed into ancestors of feat/api-tests (base: main)
descendants restackedUnder the hood:
- The fixup landed on the right commit in
feat/api-handlers. feat/api-testswas rebased onto the rewrittenfeat/api-handlers.- Your working tree is clean.
Picking a base
If you want absorb to consider only a portion of the stack, pass --base:
sm absorb --base feat/api-handlersNow git-absorb will not consider commits below feat/api-handlers as fixup targets.
When absorb can't help
git-absorb works on hunks, not whole files. If your fix touches code that was added across multiple commits, absorb may decline to find a single target — it will print which hunks were absorbed and which were left behind.
For those leftover hunks, the fallback is to commit them on the current tip with sm modify, or to use git commit --fixup=<sha> manually.
On conflict
If restacking descendants conflicts after the absorb, the standard pause protocol kicks in:
⚠ rebase paused on feat/api-testsResolve, git add, sm continue.
See also
sm absorb— flag reference.sm modify— when the fix belongs on the current tip.- git-absorb upstream — the underlying tool.
