Merge fi in the object database - #16
Merged
Merged
Conversation
chris-peterson
force-pushed
the
isolate-fi-merges-from-the-working-tree
branch
from
September 16, 2026 03:58
5999a81 to
66dc3b5
Compare
chris-peterson
force-pushed
the
isolate-fi-merges-from-the-working-tree
branch
from
September 17, 2026 22:19
66dc3b5 to
acdf32a
Compare
Every mutating command rebuilds the shared `fi` branch from scratch. That rebuild now happens in the object database — `merge-tree --write-tree` per branch onto an accumulator, `commit-tree` the result, push the sha to `refs/heads/fi` — rather than checking out a temporary `fi` branch in the caller's working tree. The checkout charged twice. It refused to run on a dirty index, so you stashed to do something unrelated to your own work, and the merge never wanted local state anyway: it builds entirely from `origin/` refs. The restore also sat inside the success path rather than a `finally`, so a rejected push or a Ctrl-C left you parked on a local `fi` branch holding a merge you never asked to be standing on. Nothing outside the object store is touched now, so there is nothing to clean up on any path. The merge and the conflict attribution are one traversal. A clean walk yields the tree to commit; a failing branch is attributed and left out of the accumulated set so the walk carries on and names every branch at fault. Where a probe cannot run at all, branches already attributed are still reported rather than discarded for the bare "nothing names the branch at fault". Refs reach git by their full names. `refs/heads/` resolves ahead of `refs/remotes/`, so a local branch called `origin/feature` wins the short form, and the merge would put work that was never pushed into the branch everyone shares — with git's ambiguity warning going to a stderr that is discarded off `--debug`. `%(refname:short)` moves for the same reason: it shortens only as far as the name stays unambiguous, so the same decoy makes a live branch read as deleted. Both listings key on the full ref instead. `commit-tree` reads none of the signing configuration `git commit` honors, so `commit.gpgsign` is read and `-S` passed explicitly. Without it a repository that signs its commits would have fi quietly stop being signed, and a forge enforcing signatures would refuse the push with nothing saying why. Local drift is warned about rather than refused. Losing the dirty-tree refusal loses the nudge to look at your own branch before integrating it, so a branch the action names that has drifted from the ref fi actually merges says so. Shared history is established with `merge-base` first, because `rev-list --left-right --count` answers a disjoint pair with the size of each side rather than failing, and every commit on both branches is not drift. A rename on one branch against an edit to the same file on another now merges cleanly: `merge-tree` is ort, where octopus had no rename detection. Closes #15
chris-peterson
force-pushed
the
isolate-fi-merges-from-the-working-tree
branch
from
September 18, 2026 01:01
acdf32a to
08b5bc1
Compare
chris-peterson
marked this pull request as ready for review
September 18, 2026 01:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
git-fi keeps a shared
fibranch: everyone's in-flight branches merged together, so two people's conflicting work surfaces now rather than at release time. Every mutating command (--add,--remove,--force,--again) rebuilds it from scratch. This moves that rebuild into the object database —merge-tree --write-treeper branch onto an accumulator,commit-treethe result, push the sha torefs/heads/fi— instead of checking out a temporaryfibranch in the caller's own working tree.The checkout charged twice. It refused to run at all on a dirty index, so you stashed to do something unrelated to your own work, like adding a teammate's branch, and the merge never wanted local state anyway: it builds entirely from
origin/refs. Worse, the restore sat inside the success path rather than afinally, so a rejected push or a Ctrl-C left you parked on a localfibranch holding a merge you never asked to be standing on. Nothing outside the object store is touched now, so there is nothing to clean up on any path.Closes #15. Supersedes #13, which asked for shell-quoting the untracked filenames in the merge-failure cleanup output: no files are written, so there is nothing left to quote.
Review guide
Core change, in order of criticality
src/readiness.ts— the walk. The merge and the conflict attribution are one traversal now: a clean walk yields the tree to commit, and a failing branch is attributed and left out of the accumulated set so the walk carries on. The previous flow ran the octopus, threw the result away, then re-walked every branch asmerge-treeprobes to find out who was at fault.src/merge.ts— commit the accumulated tree withorigin/<default>plus each merged branch as parents, then push that sha.mergeProcessloses its checkout / commit / reset / restore entirely, and the empty-branch-list case collapses into the same path rather than having one of its own.Behavior change worth knowing about
merge-treeis ort, which does. The report's apology for that case goes away with the defect —test/readiness.test.ts.Added during review
src/git.tsand the warning it feeds atsrc/merge.ts. Losing the dirty-tree refusal also loses the nudge to look at your own branch before integrating it, so warn instead when a branch the action names has drifted from the reffiactually merges. Ahead is what costs you — those commits reach nothing. Behind meansfiintegrated a newer branch than your checkout. Only branches the action names are checked, so--againstays quiet rather than reporting every stale local copy of a teammate's branch.Supporting
test/cli.test.ts— one test per acceptance criterion in Isolate fi merges from the caller's working tree #15: a dirty tree proceeds untouched,HEADand the localfibranch are unaffected, a rejected push strands nothing, a SIGTERM mid-run leaves the tree as it was. Plus the tree-and-parents parity check against what the octopus merge writes for the same inputs.SPEC.md—MERGE-02inverts from the clean-index precondition to the isolation guarantee;ADD-01,MERGE-03andMERGE-12have nothing left to require;READY-08is the new warning.STATUS.mdand thedocs/pages follow.Approach & trade-offs
A temporary
git worktreegives the same isolation and is the more obvious shape, but it still creates something that has to be torn down, so the failure mode becomes a cleanup problem rather than a non-problem: a run killed mid-flight leaves a worktree holding thefibranch name, and the next run fails withfatal: 'fi' is already used by worktree at ...until something prunes it. It also keeps the octopus strategy and its rename blindness, and materializes a full checkout on every run, LFS smudge included.merge-tree --write-treeis git 2.38.0 and the floor inengines.gitis already 2.41.0, so this doesn't move it.Testing
239 tests, across Node 22/24/26 on both ubuntu and windows.
npm testalso runstscand the generated-file check.