diff --git a/bin/gstack-pr-title-rewrite.sh b/bin/gstack-pr-title-rewrite.sh index 4725ed7205..8251f621f2 100755 --- a/bin/gstack-pr-title-rewrite.sh +++ b/bin/gstack-pr-title-rewrite.sh @@ -5,10 +5,18 @@ # Output: corrected title on stdout. # # Rule: PR titles MUST start with v. Three cases: -# 1. Already starts with "v " -> no change. -# 2. Starts with a different "v " prefix -> replace prefix. +# 1. Already starts with "v" -> no change. +# 2. Starts with a different "v" prefix -> replace prefix. # 3. No version prefix -> prepend "v ". # +# Each version prefix may be followed by a space (then a description) OR sit at +# the end of the title as a bare version with no description (e.g. "v1.2.3", the +# format ship/CHANGELOG uses for version-only bumps). Both forms must be handled +# in cases 1 and 2, otherwise a bare version falls through to case 3 and gets a +# second prefix prepended, e.g. "v1.2.3" -> "v1.2.3.4 v1.2.3". The CI workflow +# .github/workflows/pr-title-sync.yml feeds real PR titles through this and then +# `gh pr edit`s the result, so the duplicated title would be written back. +# # The version-prefix regex matches two or more dot-separated digit segments # (covers v1.2, v1.2.3, v1.2.3.4) so the rule is portable across repos that # use 3-part or 4-part versions, but does NOT strip plain words like @@ -33,12 +41,20 @@ fi # Literal prefix match (case statement is glob-quoted by bash, but our # regex-validated NEW_VERSION has no glob metacharacters so this is safe). +# Match both "v " and a bare "v" title. case "$TITLE" in - "v$NEW_VERSION "*) + "v$NEW_VERSION "*|"v$NEW_VERSION") printf '%s\n' "$TITLE" exit 0 ;; esac -REST=$(printf '%s' "$TITLE" | sed -E 's/^v[0-9]+(\.[0-9]+)+ //') -printf 'v%s %s\n' "$NEW_VERSION" "$REST" +# Strip an existing different version prefix whether it is followed by a space +# (then a description) or sits at the end of the title (bare version). +REST=$(printf '%s' "$TITLE" | sed -E 's/^v[0-9]+(\.[0-9]+)+( |$)//') +if [ -n "$REST" ]; then + printf 'v%s %s\n' "$NEW_VERSION" "$REST" +else + # Title was nothing but a (different) version prefix; emit the bare new one. + printf 'v%s\n' "$NEW_VERSION" +fi diff --git a/test/pr-title-rewrite.test.ts b/test/pr-title-rewrite.test.ts index 28a7b61a24..cfdef402e8 100644 --- a/test/pr-title-rewrite.test.ts +++ b/test/pr-title-rewrite.test.ts @@ -24,6 +24,23 @@ describe('gstack-pr-title-rewrite', () => { expect(rewrite('1.2.3.4', 'v1.2.3 feat: foo').stdout).toBe('v1.2.3.4 feat: foo'); }); + test('bare correct version (no description): no change, not duplicated', () => { + // CHANGELOG/ship uses a version-only title for branch-ahead bumps. It must + // stay as-is, not become "v1.2.3.4 v1.2.3.4". + expect(rewrite('1.2.3.4', 'v1.2.3.4').stdout).toBe('v1.2.3.4'); + }); + + test('bare different version (no description): replaces, not duplicates', () => { + // Must strip the stale prefix even with nothing after it, otherwise CI + // writes back "v1.2.3.4 v1.2.3". + expect(rewrite('1.2.3.4', 'v1.2.3').stdout).toBe('v1.2.3.4'); + }); + + test('idempotent on a bare version title', () => { + const once = rewrite('1.2.3.4', 'v1.2.3').stdout; + expect(rewrite('1.2.3.4', once).stdout).toBe(once); + }); + test('no version prefix: prepends', () => { expect(rewrite('1.2.3.4', 'feat: foo').stdout).toBe('v1.2.3.4 feat: foo'); });