From d646fd1ec4399f1161ca29621c8d36c0f7d9045f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 15 Sep 2026 22:14:51 +0000 Subject: [PATCH] :recycle: Let create:branch start from an ahead main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `create:finish` leaves the merge local until `create:release` pushes it, so `main` is routinely ahead of its upstream between a merge and a release. The branch front door demanded an exact match and refused until it was pushed, forcing a manual `git push` that the model deliberately keeps out of it. Reject only a `main` that is behind its upstream — matching `create:finish`, which already tolerates being ahead — and record why pushing from `finish` was rejected instead. The push stays with `create:release`, so the merge remains reviewable locally, and the known issue about the deadlock is gone. Resolves: backlog task "Resolve the finish/push tension". --- CHANGELOG.md | 1 + CONTRIBUTING.md | 10 +++++----- backlog.tasks | 2 +- development/workflow.md | 14 +++++++------- scripts/branch.sh | 12 +++++++----- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02dcded..fb5c80a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - require a short summary under `[Unreleased]` in the changelog before a branch is finished +- let `create:branch` start from a `main` that is ahead of its upstream, so a finished merge no longer blocks the next branch until it is pushed ## [0.1.6] - 2026-09-15 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 753b2e1..b8d8f86 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -184,9 +184,10 @@ request workflow on Gitea yet. - **Branch naming:** `feature/` / `fix/` / `chore/` - **Starting work:** `npm run create:branch -- /`. It refuses, without changing anything, unless the working tree is clean, no - merge/rebase/cherry-pick is in progress, `main` matches its upstream, and - `npm run test` is green on `main`. The prefix is _your_ call, inferred from the - task; the script validates it rather than guessing it. + merge/rebase/cherry-pick is in progress, `main` is not behind its upstream + (a local merge not yet pushed is fine — the push belongs to `create:release`), + and `npm run test` is green on `main`. The prefix is _your_ call, inferred from + the task; the script validates it rather than guessing it. - **Merging:** `npm run create:finish` (on the branch). It re-asserts the same preconditions, merges `--no-ff`, runs `npm run verify`, and deletes the branch only after the merge is green. The push is left to `create:release`, so the @@ -196,8 +197,7 @@ request workflow on Gitea yet. - **Releases are NOT triggered by pushes.** Only the maintainer triggers a release; see [Publishing](#publishing). -Full rationale, including the front-door decisions and a known issue about -`main` being ahead of its upstream between a merge and the next push: +Full rationale, including the front-door decisions: [development/workflow.md § Branching model](./development/workflow.md#branching-model). ## Submitting changes diff --git a/backlog.tasks b/backlog.tasks index 4c30368..6ec352e 100644 --- a/backlog.tasks +++ b/backlog.tasks @@ -29,7 +29,7 @@ Documentation: ☐ Validate code fences in Markdown (start with README.md) — compile the TypeScript examples against `src/` so the docs cannot drift from the API Workflow: -☐ Resolve the finish/push tension: `create:finish` leaves `main` ahead of its upstream while `create:branch` refuses until `main` matches upstream — decide whether `finish` should push or `branch` should compare only `BEHIND` (see development/workflow.md) +✔ Resolve the finish/push tension: `create:finish` leaves `main` ahead of its upstream while `create:branch` refuses until `main` matches upstream — decide whether `finish` should push or `branch` should compare only `BEHIND` (see development/workflow.md) @done Maintenance: ☐ Serve CI coverage over a tiny self-hosted webserver (replace the zip artifact) @low diff --git a/development/workflow.md b/development/workflow.md index c18502a..b91e4ec 100644 --- a/development/workflow.md +++ b/development/workflow.md @@ -26,7 +26,10 @@ plus hand-written `git`. gate is not paid on an ineligible tree. - The merge half owns the post-merge `npm run verify`, so a merge cannot land unverified. The push stays with `create:release` so the merge is reviewed - locally first. + locally first; `main` is therefore routinely ahead of its upstream between a + merge and the release that ships it. `create:branch` requires only that `main` + is not _behind_ — matching `create:finish`, which tolerates the local merge and + fast-forwards over a remote one — rather than an exact match. - Every failure is non-mutating except the baseline test, which runs on `main` after switching there: a red `main` restores the branch you started on, and a merge conflict aborts back to the feature branch rather than stranding a @@ -42,12 +45,9 @@ plus hand-written `git`. can run from a dirty tree. - Fast-forward instead of `--no-ff`: `--no-ff` keeps each unit of work visible in `git log`. - -#### Known issue - -- `create:finish` does not push, so `main` is ahead of `origin/main` between a - merge and the next push. `create:branch` requires `main` to match its upstream - and refuses until it is pushed; push `main` before starting the next branch. +- Pushing from `create:finish` to keep `main` level with its upstream: it would + trade the local review the push waits for for a network side effect, and a + failed push would leave the merge landed but unpublished. ## Changelog notes diff --git a/scripts/branch.sh b/scripts/branch.sh index 19d394a..4c7cd19 100755 --- a/scripts/branch.sh +++ b/scripts/branch.sh @@ -86,12 +86,14 @@ if [ -n "${UPSTREAM}" ]; then echo " refusing to branch on a possibly stale '${BASE}'." >&2 exit 1 } + # Only *behind* is a problem. `create:finish` deliberately leaves the local + # merge on '${BASE}' until `create:release` pushes it, so being ahead is the + # normal state between a merge and the release that ships it; branching from + # those commits is intended. Missing remote commits is not. BEHIND=$(git rev-list --count "${BASE}..${UPSTREAM}") - AHEAD=$(git rev-list --count "${UPSTREAM}..${BASE}") - if [ "${BEHIND}" -ne 0 ] || [ "${AHEAD}" -ne 0 ]; then - echo "error: '${BASE}' has diverged from '${UPSTREAM}' (ahead ${AHEAD}, behind ${BEHIND})." >&2 - [ "${AHEAD}" -ne 0 ] && echo " not yet pushed commits on '${BASE}': push them, or rebase this work onto them." >&2 - [ "${BEHIND}" -ne 0 ] && echo " update it: git switch ${BASE} && git pull --ff-only" >&2 + if [ "${BEHIND}" -ne 0 ]; then + echo "error: '${BASE}' is behind '${UPSTREAM}' (by ${BEHIND})." >&2 + echo " update it: git switch ${BASE} && git pull --ff-only" >&2 exit 1 fi else