Say where each branch stands, and whose rebase fixes a failure - #11
Draft
chris-peterson wants to merge 1 commit into
Draft
Say where each branch stands, and whose rebase fixes a failure#11chris-peterson wants to merge 1 commit into
chris-peterson wants to merge 1 commit into
Conversation
chris-peterson
force-pushed
the
merge-readiness
branch
from
August 31, 2026 17:32
c21deb1 to
3ab2a0d
Compare
git-fi maintains a shared `fi` branch that merges everyone's in-flight feature branches together, so conflicts between them surface while both branches are still small. When that combined merge fails it prints the whole set it was merging, because git cannot attribute a conflict inside an octopus merge to a single branch, and the usual response on a team is `git fi -f <my-branch>`: replace fi with just your branch and move on, discarding everyone else's integration. The conflict is still there the next time someone adds their branch back. This makes fi say which branch actually failed, who owns fixing it, and how, which is usually one or two rebases rather than a reset. Attribution replays the branch list one at a time onto `origin/main` with `git merge-tree --write-tree`, which merges in the object database and needs no ref, index, or working tree, so it runs after the failed merge is cleaned up and disturbs nothing. A failing branch is probed against `origin/main` alone to separate "needs a rebase" from "collides with a peer", then swept pairwise to name which peer, and is left out of the accumulation so one bad branch does not condemn everything after it. Two things the replay cannot say, it says explicitly rather than implying. `git merge-tree` exits 1 for an unresolvable ref and a shallow clone's unrelated histories as well as for a conflict, so a probe that wrote no tree OID is treated as unattributable and abandons the pass — filing that branch as conflicting with main would propagate, since it is then left out of the set every later branch is measured against. And the combined merge uses the octopus strategy, which has no rename detection where merge-tree's engine has it, so a rename against a concurrent edit fails the merge and comes back clean from every probe; the report names the combination instead of naming nobody. The remedies are lines a person pastes into a shell, so branch names in them are single-quoted: a ref name may hold backticks, `;`, `&&` or a leading `-`, and inside double quotes a backtick still expands. Author emails get the same treatment from the other direction — git accepts ANSI escapes in one and `fsck --strict` passes them, so control characters are stripped as listing fields are read, before anything can repaint text already written to the terminal. The `git fi -r` escape hatch is offered only for branches fi actually holds; one that failed on the way in was never added. One `git branch -r` listing now carries the behind count, merged-ness and the tip author alongside the name and date, so those cost no extra invocation and `git branch -r --merged` goes away. Counts git cannot produce are null rather than zero, which is a real position: no `origin/<default>` to compare against, a branch since deleted, or — for the behind count alone — a shallow clone, whose walk stops at the graft and would otherwise report the size of the fetched window. The ahead count survives truncation because the only thing derived from it is merged-ness, and a truncated window makes a branch look more ahead, so pruning can miss a landed branch but never drops a live one. The git floor moves from 2.13.0 to 2.41.0 for `%(ahead-behind:)`. The cost is real but smaller than it looks: `merge-tree` alone requires 2.38.0, which already excludes Ubuntu 22.04's 2.34.1, so dropping the count would move the floor by three releases rather than back to 2.13.0. `--json` nests everything under the branch instead of spreading it across arrays keyed by name, which made a consumer join to answer "what is the state of this branch" and let the arrays disagree about which branches exist. On a failed merge `branches` is fi as it still stands, since nothing was pushed, and the set that was tried is named `attempted`.
chris-peterson
force-pushed
the
merge-readiness
branch
from
August 31, 2026 19:14
3ab2a0d to
39d0983
Compare
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 maintains a shared
fibranch that merges everyone's in-flight feature branches together, so conflicts between them surface while both branches are still small instead of at release time. When that combined merge fails, though, it prints the whole set it was merging — git can't attribute a conflict inside one octopus merge to a single branch — and the usual response on a team isgit fi -f <my-branch>: replacefiwith just your branch and move on, discarding everyone else's integration. The conflict is still there the next time someone adds their branch back. This makesfisay which branch actually failed, who owns fixing it, and how — usually one or two rebases rather than a reset.Review guide
Start here — the attribution itself
src/readiness.tsattributeConflicts— replays the branch list one at a time ontoorigin/main, then probes each failure againstorigin/mainalone to split "needs a rebase" from "collides with a peer", and sweeps pairwise to name which peer. A failing branch is left out of the accumulation, so one bad branch doesn't condemn everything after it.src/readiness.tsmergeTree— the whole thing runs ongit merge-tree --write-tree, which merges in the object database and exits 1 on conflict. No ref, no index, no working tree, so it runs after the failed merge is cleaned up and disturbs nothing.src/merge.ts— where it hooks into theMERGE-11failure path.The report a user actually sees
src/readiness.tsrenderConflicts— each branch carries its tip author's email, so the line says who owns the fix; the report closes with agit fi -rline marked temporary.--forceis offered nowhere.One listing answers three questions
src/git.tsthe--formatstring —%(ahead-behind:)and%(authoremail:trim)ride on thegit branch -rlisting that already runs, so the behind count, merged-ness, and the author cost no extra invocation.src/git.tsmergedRemoteBranches— a branch with nothing ahead ofmainis exactly whatgit branch -r --mergedreports, so the ahead half replaces that second invocation. Worth a look: this changes the mechanism behindMERGE-07's existing prune.src/style.ts—↓12for a branch trailingmain, a struck-through name plusmergedfor one that's landed. The word rides with the strikethrough because not every terminal draws SGR 9, andmergedsupersedes the behind count rather than stacking with it.Skim
src/readiness.tsbranchJson—--jsonnow nests everything under the branch; the top-levelciarray is gone. Parallel arrays keyed by name made a consumer join to answer "what's the state of this branch", and could disagree about which branches exist. Nothing consumes this output yet.SPEC.mdMerge Readiness —READY-01..READY-07, plusFUT-01capturing--check(answer "will my add land?" before attempting it) as deferred.test/readiness.test.ts— the discriminating cases: a branch clean againstmainbut colliding with a peer is blamed on the peer, and a clean branch merged earlier in the same run isn't blamed at all.Approach & trade-offs
The git floor moves from 2.13.0 to 2.41.0 (
PRE-02,src/git.ts). That's the price of a commit count rather than a bare behind/not-behind flag:%(ahead-behind:)landed in 2.41.0, released 2023-06-01.git merge-tree --write-treeneeds only 2.38.0, so attribution isn't what sets the floor.Who that excludes: Ubuntu 22.04 ships git 2.34.1. macOS is fine either way — Apple's CLT git and Homebrew's are both well past it. The cheaper alternative is
git branch -r --contains, which keeps the 2.13.0 floor and gives a boolean instead of↓12.