no way to resync a stale local branch to its upstream without raw git #571

Open
opened 2026-09-21 21:23:21 +00:00 by hexajon · 0 comments
Owner

The gap

A local main that has diverged from origin/main cannot be brought back in line through any madtea surface. madt_status reports the divergence and then hands the user off to raw git:

WARNING: main is 153 commit(s) ahead of origin/main - local commits on the
default branch have not been pushed.
To inspect: `git log origin/main..main` in your shell. madtea carries no
ref-rewind; push or reset from your own shell as needed.

"Use your own shell" is the wrong answer for the tool that exists so nobody has to. Every other git operation on this path has a madtea surface: madt_pull, madt_push, madt_fetch, madt_branch_create, madt_branch_delete, madt_worktrees, madt_commit. Reconciling a branch with its upstream is the one common operation that falls out of the set, and it falls out exactly when a repo is in the state most likely to cause damage.

Nothing else covers it:

  • madt_pull merges, and rebase=true replays the local commits. With a branch that is ahead AND behind, both produce a wrong result: a merge commit over commits that should not exist, or 153 rebased duplicates.
  • madt_push --force-with-lease pushes the wrong direction.
  • madt_branch_delete refuses an unmerged branch, and cannot touch a checked-out default branch anyway.

The real case

madtea-site's local main sits ahead 153 and behind 155. The 153 are stale duplicates: every PR was squash-merged, so the content landed upstream under different SHAs. git ls-tree on both refs confirms local main holds no unique content at all. The correct action is to discard those 153 and take the remote, and there is no way to ask madtea to do it.

It is not a cosmetic problem. In that state madt_finish dir=<primary> is blocked outright by an identity guard that reads the primary's HEAD and finds 127 commits authored under a since-retired identity. The repo is unusable through its normal surface until a human runs raw git.

Proposed shape

A resync that is safe by construction, refusing whenever local-only work would be lost:

madt_pull(reset=true)        # or madt_branch_resync(to="origin/main")

It should:

  1. Establish whether the local-only commits carry any content the upstream lacks.
  2. Hard-reset to the upstream when they do not, reporting how many commits were discarded and why that was safe.
  3. REFUSE when they do, naming the commits and the unique paths, the way madt_branch_delete already refuses an unmerged branch. No force flag on this surface.
  4. Leave the stash stack alone, and say so, since a reset does not touch it.

The safety analysis already exists in the codebase. madt_branch_delete's refusal returns cherry_equivalent, tree_identical, likely_squash_merged and ahead_unique. That is precisely the squash-merge detection this needs. The new surface should reuse it rather than grow a second implementation.

Acceptance criteria

  • A branch whose local-only commits are all cherry-equivalent or tree-identical upstream resyncs in one call, reporting the discarded count.
  • A branch carrying genuinely unique content is REFUSED, naming the commits and paths, with no force override on this surface.
  • The refusal returns structured fields, matching madt_branch_delete's refusal shape, so callers branch on fields and not prose.
  • Works on the default branch, which is the case that motivates it.
  • madt_status's "push or reset from your own shell as needed" is replaced by a pointer to this call.
  • A foreign primary checkout stays refused, consistent with ADR 0019.
## The gap A local `main` that has diverged from `origin/main` cannot be brought back in line through any madtea surface. `madt_status` reports the divergence and then hands the user off to raw git: ``` WARNING: main is 153 commit(s) ahead of origin/main - local commits on the default branch have not been pushed. To inspect: `git log origin/main..main` in your shell. madtea carries no ref-rewind; push or reset from your own shell as needed. ``` "Use your own shell" is the wrong answer for the tool that exists so nobody has to. Every other git operation on this path has a madtea surface: `madt_pull`, `madt_push`, `madt_fetch`, `madt_branch_create`, `madt_branch_delete`, `madt_worktrees`, `madt_commit`. Reconciling a branch with its upstream is the one common operation that falls out of the set, and it falls out exactly when a repo is in the state most likely to cause damage. Nothing else covers it: - `madt_pull` merges, and `rebase=true` replays the local commits. With a branch that is ahead AND behind, both produce a wrong result: a merge commit over commits that should not exist, or 153 rebased duplicates. - `madt_push --force-with-lease` pushes the wrong direction. - `madt_branch_delete` refuses an unmerged branch, and cannot touch a checked-out default branch anyway. ## The real case madtea-site's local `main` sits ahead 153 and behind 155. The 153 are stale duplicates: every PR was squash-merged, so the content landed upstream under different SHAs. `git ls-tree` on both refs confirms local `main` holds no unique content at all. The correct action is to discard those 153 and take the remote, and there is no way to ask madtea to do it. It is not a cosmetic problem. In that state `madt_finish dir=<primary>` is blocked outright by an identity guard that reads the primary's HEAD and finds 127 commits authored under a since-retired identity. The repo is unusable through its normal surface until a human runs raw git. ## Proposed shape A resync that is safe by construction, refusing whenever local-only work would be lost: ``` madt_pull(reset=true) # or madt_branch_resync(to="origin/main") ``` It should: 1. Establish whether the local-only commits carry any content the upstream lacks. 2. Hard-reset to the upstream when they do not, reporting how many commits were discarded and why that was safe. 3. REFUSE when they do, naming the commits and the unique paths, the way `madt_branch_delete` already refuses an unmerged branch. No force flag on this surface. 4. Leave the stash stack alone, and say so, since a reset does not touch it. **The safety analysis already exists in the codebase.** `madt_branch_delete`'s refusal returns `cherry_equivalent`, `tree_identical`, `likely_squash_merged` and `ahead_unique`. That is precisely the squash-merge detection this needs. The new surface should reuse it rather than grow a second implementation. ## Acceptance criteria - [ ] A branch whose local-only commits are all cherry-equivalent or tree-identical upstream resyncs in one call, reporting the discarded count. - [ ] A branch carrying genuinely unique content is REFUSED, naming the commits and paths, with no force override on this surface. - [ ] The refusal returns structured fields, matching `madt_branch_delete`'s refusal shape, so callers branch on fields and not prose. - [ ] Works on the default branch, which is the case that motivates it. - [ ] `madt_status`'s "push or reset from your own shell as needed" is replaced by a pointer to this call. - [ ] A foreign primary checkout stays refused, consistent with ADR 0019.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#571
No description provided.