Skip to content

feat: show how far a worktree's base branch has moved - #185

Merged
omartelo merged 3 commits into
mainfrom
feat/base-status
Aug 8, 2026
Merged

feat: show how far a worktree's base branch has moved#185
omartelo merged 3 commits into
mainfrom
feat/base-status

Conversation

@omartelo

@omartelo omartelo commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Running several worktrees at once, the first branch to land leaves every other one stale in silence. Nothing in lich knew about the base branch until GitHub answered BEHIND or DIRTY at the Merge button, one pull request at a time — and by then the collision costs an agent its context, once per worktree.

The session card's branch row now carries the answer, computed locally: how many commits origin/main has picked up since, or — when a merge would collide — how many files it would collide on. The card's tooltip names the base branch, the commit count and up to three of the conflicting paths. Knowing which branches will fight is what lets the landing order be chosen deliberately: one conflict to resolve instead of one per worktree.

How it works

git merge-tree --write-tree computes the merge in the object database alone — no worktree, no index, no HEAD. Exit status 1 is git's "merged with conflicts" and the only status carrying a file list; a git too old to know the flag degrades the readout to the behind count rather than failing it.

Three things keep it off the poller's subprocess budget, which the git-status store already spends carefully:

  • One subprocess in the hot path. rev-parse --path-format=absolute --git-common-dir HEAD refs/remotes/origin/HEAD answers all three questions at once.
  • The answer is memoised on the two commits it was computed from. The poll runs every second and merge-tree writes tree objects on every call.
  • A base that has not moved skips merge-tree entirely. Nothing to merge in means nothing to collide with.

Nothing else in lich fetches after a worktree is created, and a branch lands on the forge rather than here — so a throttled background fetch (once per 2 minutes per repository, off the poll goroutine, 30s timeout) keeps the remote refs current. Without it the readout would answer from refs frozen at creation time and never notice the one thing it exists to report. That is new behaviour in the user's own repository, so it is recorded under Known Ceilings.

What is deliberately not here

  • No "Update from base" button. The agent sitting in that worktree types git merge origin/main in one line. lich has never written to git outside the worktree flows and this is not the change that starts.
  • No worktree-versus-worktree collision. Two branches that will fight each other before either lands is the interesting question, and it is O(n²) and unproven — the base readout catches the same collision one landing later.
  • Nothing on the Pulls screen. merge-gate.ts already prints "Base branch has moved" from GitHub's own answer.

Ceilings

  • The merge is computed between commits, so an agent mid-edit reads as whatever it last committed. The mark answers "will this collide when committed as it stands", not "is colliding right now".
  • The base is always origin/HEAD. A branch stacked on another feature branch is measured against the default branch and its count is wrong.
  • No origin, or no origin/HEAD: the service answers nil and the readout is absent, matching the "hide the segment" contract Branch and PullRequest already keep.

Test plan

  • go test -race ./... — 12 new tests in internal/project, driving real git through a clone-and-origin pair: no remote, not a repository, up to date, behind without conflict, behind with conflict, memo invalidation on each of the two keys, merge-tree -z parsing pinned to the bytes git emits, fetch throttling.
  • pnpm test — 7 new tests on baseReadout (the pure readout the card renders) plus 2 on the store, covering a base that moves under an otherwise unchanged checkout and a conflict list that changes without changing length.
  • gofmt -l ., go vet ./..., pnpm check, pnpm build clean.
  • Cross-compile loop for linux/darwin/windows — commandContext is new in internal/project/git.go and rides the winexec seam, hence ci:os.
  • Verified by hand in task dev against worktrees that were clean, behind, and colliding.

Running several worktrees at once, the first branch to land leaves every
other one stale in silence: nothing in lich knew about the base branch
until GitHub answered BEHIND or DIRTY at the Merge button, one pull
request at a time. Resolving that collision costs an agent its context,
and paying it once per worktree is the tax parallel work actually runs
into.

The session card's branch row now carries the answer, computed locally:
how many commits origin/main has picked up since, and — when a merge
would collide — how many files it would collide on. The card's tooltip
names the base branch, the commit count and up to three of the
conflicting paths. Knowing which branches will fight is what lets the
landing order be chosen deliberately.

merge-tree computes the merge in the object database alone, touching no
worktree, index or HEAD. The answer is memoised on the two commits it
was computed from, because the poll behind it runs every second and
merge-tree writes tree objects on every call; a base that has not moved
skips it entirely. Nothing else in lich fetches, so a throttled
background fetch keeps the remote refs current — without it the readout
would answer from refs frozen at worktree creation and never notice the
one thing it exists to report.

Two ceilings, both in CHANGELOG.md: the merge is computed between
commits, so an agent mid-edit reads as its last commit; and the base is
always origin/HEAD, so a branch stacked on another feature branch is
measured against the wrong one.
@omartelo omartelo added the ci:os Runs the backend suite on Windows and macOS runners label Aug 8, 2026
@omartelo
omartelo merged commit c263891 into main Aug 8, 2026
4 checks passed
@omartelo
omartelo deleted the feat/base-status branch August 8, 2026 14:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:os Runs the backend suite on Windows and macOS runners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant