Skip to content

Say where each branch stands, and whose rebase fixes a failure - #11

Draft
chris-peterson wants to merge 1 commit into
mainfrom
merge-readiness
Draft

Say where each branch stands, and whose rebase fixes a failure#11
chris-peterson wants to merge 1 commit into
mainfrom
merge-readiness

Conversation

@chris-peterson

@chris-peterson chris-peterson commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Context

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 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 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 — usually one or two rebases rather than a reset.

Review guide

Start here — the attribution itself

  • src/readiness.ts attributeConflicts — replays the branch list one at a time onto origin/main, then probes each failure against origin/main alone 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.ts mergeTree — the whole thing runs on git 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 the MERGE-11 failure path.

The report a user actually sees

  • src/readiness.ts renderConflicts — each branch carries its tip author's email, so the line says who owns the fix; the report closes with a git fi -r line marked temporary. --force is offered nowhere.
Failed trying to merge branch(es):

 * feature-a (bob@example.com)  conflicts with main
     * src/config.ts
     git checkout feature-a && git rebase origin/main && git push --force-with-lease
 * feature-c (cara@example.com)  conflicts with feature-b (bob@example.com)
     * src/router.ts
     rebase feature-c onto feature-b (or the reverse) and settle the overlap there

Or temporarily remove them from fi — the conflict comes back when they do:
  git fi -r feature-a feature-c

One listing answers three questions

  • src/git.ts the --format string%(ahead-behind:) and %(authoremail:trim) ride on the git branch -r listing that already runs, so the behind count, merged-ness, and the author cost no extra invocation.
  • src/git.ts mergedRemoteBranches — a branch with nothing ahead of main is exactly what git branch -r --merged reports, so the ahead half replaces that second invocation. Worth a look: this changes the mechanism behind MERGE-07's existing prune.
  • src/style.ts↓12 for a branch trailing main, a struck-through name plus merged for one that's landed. The word rides with the strikethrough because not every terminal draws SGR 9, and merged supersedes the behind count rather than stacking with it.

Skim

  • src/readiness.ts branchJson--json now nests everything under the branch; the top-level ci array 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.md Merge ReadinessREADY-01..READY-07, plus FUT-01 capturing --check (answer "will my add land?" before attempting it) as deferred.
  • test/readiness.test.ts — the discriminating cases: a branch clean against main but 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-tree needs 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.

A failed merge printed the whole set it was merging, because the
combined `git merge` can't tell which branch caused the conflict. The
usual response to that is `git fi -f <my-branch>`: replace fi with one
branch, discard everyone else's integration, and meet the same conflict
again the next time someone adds theirs back.

Follow the failure with a second pass that can attribute it. Merging the
list one branch at a time with `git merge-tree --write-tree` separates
"this branch needs a rebase onto main" from "these two branches
overlap", and a pairwise sweep names the actual peer rather than the
whole accumulated set. It reads the object database only — no ref, no
index, no working tree — so it runs after the tree is restored and
disturbs nothing. Each failing branch comes back with its conflicted
paths, the email of whoever last moved it — so the report says who owns
the fix — and the remedy its case calls for, closing with the `git fi -r`
line that takes the failing branches out temporarily. `--force` is
offered nowhere, and the docs argue against it rather than merely
omitting it.

The list gains two per-branch markers for where a branch stands before
anything is attempted: `↓N` for how far it trails the default branch,
which is the precondition for the conflict and so says which branch to
rebase first; and a struck-through name marked `merged` for a branch
whose commits have all landed, which is spent and which the next
mutation prunes.

Both come from `%(ahead-behind:)` on the branch listing that already
runs — the behind half for the count, the ahead half for merged-ness.
The ahead half also replaces the separate `git branch -r --merged`
invocation, since a branch with nothing ahead of the default branch is
exactly what that reports.

`--json` nests everything about a branch under the branch: `branches`
becomes an array of objects carrying `name`, `ahead`, `behind`,
`merged`, and `ci`, and the top-level `ci` array is gone. Parallel
arrays keyed by branch name made a consumer join to answer "what is the
state of this branch", and let the arrays disagree about which branches
exist. Nothing consumes this output yet, so the shape changes outright
rather than growing a compatible alias.

The atom arrived in git 2.41.0, so the floor in PRE-02 moves there from
2.13.0. That is the price of a commit count over a bare behind flag:
2.41.0 was released 2023-06-01, and a long-support distribution below it
needs a newer git installed.

`--check`, which would answer "will my add land?" before attempting it,
is captured as FUT-01 rather than built here.
@chris-peterson chris-peterson self-assigned this Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant