From 0f5921479d9add7c5066e3aed538926b22c28d91 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 25 Aug 2026 19:36:58 +0000 Subject: [PATCH] fix(pr): cut the commit range at the previous tag's date --- scripts/update-changelog.ts | 15 ++++++++++++++- test/update-changelog-main.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/scripts/update-changelog.ts b/scripts/update-changelog.ts index c7415f3..874af8f 100644 --- a/scripts/update-changelog.ts +++ b/scripts/update-changelog.ts @@ -249,11 +249,24 @@ function subjectsOnlyOn (ref: string): Set { } } +/** Committer date of the commit a tag points at, as an ISO 8601 string. */ +function tagDate (ref: string): string | null { + try { + return execFileSync('git', ['log', '-1', '--format=%cI', ref], { encoding: 'utf8', maxBuffer: MAX_BUFFER }).trim() || null + } catch { + return null + } +} + function getCommitsSince (tag: Tag | null): Commit[] { const range = tag ? `${tag.ref}..HEAD` : 'HEAD' + // Merging the base branch into a release branch (or a tag that isn't an + // ancestor of HEAD) puts commits older than the tag inside `tag..HEAD`; + // they shipped in an earlier release, so cut the range at the tag's date. + const since = tag ? tagDate(tag.ref) : null const stdout = execFileSync( 'git', - ['log', range, `--pretty=format:%H%x1f%h%x1f%an%x1f%ae%x1f%s%x1f%b%x1e`], + ['log', range, ...(since ? ['--since', since] : []), `--pretty=format:%H%x1f%h%x1f%an%x1f%ae%x1f%s%x1f%b%x1e`], { encoding: 'utf8', maxBuffer: MAX_BUFFER }, ) const commits = stdout diff --git a/test/update-changelog-main.test.ts b/test/update-changelog-main.test.ts index 053667a..01e20ea 100644 --- a/test/update-changelog-main.test.ts +++ b/test/update-changelog-main.test.ts @@ -51,6 +51,7 @@ const git = { tags: [] as string[], commits: [] as FakeCommit[], revList: [] as string[], + divergedSubjects: [] as string[], remote: 'git@github.com:owner/repo.git', branch: 'main', } @@ -85,6 +86,7 @@ function stubGit () { if (sub === 'rev-parse') return args.includes('--abbrev-ref') ? git.branch : HEAD_SHA if (sub === 'rev-list') return git.revList.join('\n') if (sub === 'log' && args[1] === '-1') return '2024-01-01T00:00:00Z' + if (sub === 'log' && args.includes('--pretty=format:%s')) return git.divergedSubjects.join('\n') if (sub === 'log') return record(...git.commits) throw new Error(`unexpected git call: ${args.join(' ')}`) }) @@ -213,6 +215,7 @@ beforeEach(() => { git.tags = ['v1.2.3'] git.commits = [FEAT] git.revList = [FEAT.hash] + git.divergedSubjects = [] git.remote = 'git@github.com:owner/repo.git' git.branch = 'main' @@ -255,6 +258,25 @@ describe('lockstep main', () => { expect(prBody()).toContain('- Ada (@ada)') }) + it('omits commits already released on the diverged branch', async () => { + const other: FakeCommit = { hash: 'b'.repeat(40), short: 'bbbbbbb', name: 'Bo', email: 'bo@example.com', subject: 'fix: cherry-picked (#8)' } + git.commits = [FEAT, other] + git.revList = [FEAT.hash, other.hash] + api.logins = new Map([['aaaaaaa', 'ada'], ['bbbbbbb', 'bo']]) + git.divergedSubjects = [other.subject] + await main() + expect(prBody()).toContain('- add a thing (#7)') + expect(prBody()).not.toContain('cherry-picked') + }) + + it('cuts the commit range at the date of the previous tag', async () => { + await main() + const calls = execFileSync.mock.calls as Array<[string, string[]]> + const args = calls.find(([, args]) => args[0] === 'log' && args[1] === 'refs/tags/v1.2.3..HEAD')![1] + expect(args).toContain('--since') + expect(args[args.indexOf('--since') + 1]).toBe('2024-01-01T00:00:00Z') + }) + it('skips when HEAD is the merge of a release PR', async () => { api.prsForHead = [{ head: { ref: 'release/v1.2.3' }, merged_at: '2024-01-01T00:00:00Z' }] await main() -- 2.51.2