Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ All notable changes to MeMesh are documented here.

## [Unreleased]

### Fixed

- **`release:finish` no longer warns about a fetch that worked.** Its last step
brought the new tag into the local checkout with `git fetch --tags origin` —
a command that asks for every tag `origin` has and exits non-zero if any one
of them cannot be written. This repository has 26 that cannot: measured
across all 73 tags, the version tags from `v2.10.1` through `v4.1.7` plus
`benchmark/longmemeval-public-r1` point at different objects locally than on
`origin`, while everything from `v4.2.0` onward agrees. The cause is a
history rewrite that removed internal documents — `origin`'s `v4.1.7` tree
lacks four files the local one still carries. So that step printed
``could not `git fetch --tags origin` `` on every release, immediately after
writing the new tag successfully. Measured on the v4.6.2 release, same
checkout: `--tags` exited 1 with 26 rejections while
`refs/tags/v4.6.2:refs/tags/v4.6.2` exited 0. It now fetches the one tag it
needs — not `--tags --force`, which would silently rewrite 26 local refs as a
side effect of cutting a release. The line that follows is unchanged and was
already sound: it asks whether the tag is in fact present rather than
treating the fetch's own report as the answer.

## [4.6.2] — 2026-08-23

### Added
Expand Down
29 changes: 26 additions & 3 deletions scripts/finish-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,34 @@ console.log(`\n✓ released: ${releaseUrl}`);
// Bring the tag into this checkout so `verify:release` here stops failing —
// the check reads `git tag --list`, and until the tag is fetched, main still
// looks like it declares an untagged version.
if (capture('git', ['fetch', '--tags', 'origin']) === null) {
console.log(' (could not `git fetch --tags origin` — run it to sync this checkout)');
//
// ONE tag, by explicit refspec, not `--tags`. The wide form asks for every tag
// the remote has and fails if ANY of them cannot be written, so one unrelated
// divergence anywhere in the repository's history turns this step red.
//
// This repository has 26 of them. Measured 2026-08-23 across all 73 tags: 26
// refs disagree with origin — the 25 version tags from v2.10.1 through v4.1.7,
// plus `benchmark/longmemeval-public-r1` — while everything from v4.2.0 onward
// agrees. The cause is a history rewrite that removed internal documents:
// origin's v4.1.7 tree lacks four files the local v4.1.7 tree still carries.
// (Their names are deliberately not repeated here — putting them back into a
// public file would undo part of what the rewrite was for.) Those old refs are
// not going to converge, so `--tags` fails here on every release — right after
// writing the new tag it was actually asked for. Measured on v4.6.2, same
// checkout: `--tags` exited 1 with 26 rejections, while
// `refs/tags/v4.6.2:refs/tags/v4.6.2` exited 0.
//
// Not `--tags --force` either: that would silently rewrite 26 local refs as a
// side effect of cutting a release. A warning that always fires is a warning
// nobody reads; the fix is to stop asking a wider question than we need.
const fetchSpec = `refs/tags/${tag}:refs/tags/${tag}`;
if (capture('git', ['fetch', 'origin', fetchSpec]) === null) {
console.log(` (could not \`git fetch origin ${fetchSpec}\` — run it to sync this checkout)`);
}
const nowTagged = (captureLines('git', ['tag', '--list', 'v*']) ?? []).includes(tag);
console.log(` local checkout has ${tag}: ${nowTagged ? 'yes' : 'no — run `git fetch --tags origin`'}`);
console.log(
` local checkout has ${tag}: ${nowTagged ? 'yes' : `no — run \`git fetch origin ${fetchSpec}\``}`
);

// Where to look next. The publish is a workflow run, and npm's registry lags
// that run by minutes: `npm view` answering with the OLD version right after a
Expand Down
24 changes: 24 additions & 0 deletions tests/release-preconditions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,30 @@ describe('finish-release cuts the release in one call', () => {
expect(code).toMatch(/'release',\s*'view'/);
});

it('fetches the one tag it needs, not every tag the remote has', () => {
// `git fetch --tags` asks for every tag origin has and exits non-zero if
// ANY of them cannot be written. Measured 2026-08-23 across all 73 tags in
// this repository: 26 refs disagree with origin (v2.10.1 through v4.1.7,
// plus benchmark/longmemeval-public-r1) because a history rewrite removed
// internal documents from those commits. So the wide form fails on every
// release, immediately after successfully writing the tag it was asked
// for, and the script printed a warning about a fetch that had worked.
//
// Both halves are pinned. The refspec must be built — delete it and this
// goes red. And `--tags` must not come back on the FETCH — the negative
// half is break-tested by INSERTING it, not by deleting the refspec,
// because a not.toMatch also passes when the thing it forbids has become
// impossible to express.
//
// Scoped to the fetch on purpose. `git ls-remote --tags origin` above is a
// different call with the same flag and is correct: listing every remote
// tag is exactly what the precondition check needs. A blanket ban on the
// string would have failed on it — as the first version of this test did.
expect(code).toMatch(/'fetch',\s*'origin',\s*fetchSpec/);
expect(code).toMatch(/refs\/tags\/\$\{tag\}:refs\/tags\/\$\{tag\}/);
expect(code).not.toMatch(/'fetch'[^\]]*'--tags'/);
});

it('is wired to an npm script, so it is the documented way in', () => {
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
expect(pkg.scripts['release:finish']).toContain('scripts/finish-release.mjs');
Expand Down
Loading