Skip to content

Merge fi in the object database - #16

Merged
chris-peterson merged 1 commit into
mainfrom
isolate-fi-merges-from-the-working-tree
Sep 19, 2026
Merged

chris-peterson merged 1 commit into
mainfrom
isolate-fi-merges-from-the-working-tree

Conversation

@chris-peterson

@chris-peterson chris-peterson commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Context

git-fi keeps a shared fi branch: 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-tree per branch onto an accumulator, commit-tree the result, push the sha to refs/heads/fi — instead of checking out a temporary fi branch 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 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.

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

  1. 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 as merge-tree probes to find out who was at fault.
  2. src/merge.ts — commit the accumulated tree with origin/<default> plus each merged branch as parents, then push that sha. mergeProcess loses 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

  1. A rename on one branch against an edit to the same file on another now merges cleanly. Octopus has no rename detection; merge-tree is ort, which does. The report's apology for that case goes away with the defect — test/readiness.test.ts.

Added during review

  1. src/git.ts and the warning it feeds at src/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 ref fi actually merges. Ahead is what costs you — those commits reach nothing. Behind means fi integrated a newer branch than your checkout. Only branches the action names are checked, so --again stays quiet rather than reporting every stale local copy of a teammate's branch.

Supporting

  1. 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, HEAD and the local fi branch 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.
  2. SPEC.md — MERGE-02 inverts from the clean-index precondition to the isolation guarantee; ADD-01, MERGE-03 and MERGE-12 have nothing left to require; READY-08 is the new warning. STATUS.md and the docs/ pages follow.

Approach & trade-offs

A temporary git worktree gives 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 the fi branch name, and the next run fails with fatal: '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-tree is git 2.38.0 and the floor in engines.git is already 2.41.0, so this doesn't move it.

Testing

239 tests, across Node 22/24/26 on both ubuntu and windows. npm test also runs tsc and the generated-file check.

@chris-peterson chris-peterson self-assigned this Sep 16, 2026
@chris-peterson chris-peterson added the enhancement New feature or request label Sep 16, 2026
@chris-peterson
chris-peterson force-pushed the isolate-fi-merges-from-the-working-tree branch from 5999a81 to 66dc3b5 Compare September 16, 2026 03:58
@chris-peterson chris-peterson added this to the 1.3 milestone Sep 17, 2026
@chris-peterson
chris-peterson force-pushed the isolate-fi-merges-from-the-working-tree branch from 66dc3b5 to acdf32a Compare September 17, 2026 22:19
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
chris-peterson force-pushed the isolate-fi-merges-from-the-working-tree branch from acdf32a to 08b5bc1 Compare September 18, 2026 01:01
@chris-peterson
chris-peterson marked this pull request as ready for review September 18, 2026 01:01
@chris-peterson
chris-peterson merged commit 783da76 into main Sep 19, 2026
9 checks passed
@chris-peterson
chris-peterson deleted the isolate-fi-merges-from-the-working-tree branch September 19, 2026 06:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Isolate fi merges from the caller's working tree

1 participant