fix: keep co-author trailers in one trailer block

gstack-coauthor-trailers --message appended the new Co-authored-by lines as a
separate paragraph after existing trailers, so git interpret-trailers (and
GitHub attribution) read only the last paragraph and the existing co-author
lost credit. The new lines now join the existing trailer block; the test
checks the result with git interpret-trailers --parse.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-26 22:20:32 +00:00
1 parent 9ea5bfded5
commit 12d0463509
2 files changed
+11 -2

No files matched your search

+5 -1
View File
@@ -77,8 +77,12 @@ function main(argv: string[]): number {
const present = new Set([...text.matchAll(/^co-authored-by:.*<([^>]+)>\s*$/gim)].map(m => m[1]!.toLowerCase()));
appended = [...authors.entries()].filter(([key]) => !present.has(key)).map(([, a]) => `Co-authored-by: ${a.name} <${a.email}>`);
if (appended.length) {
// Trailers are one paragraph: a blank line before the new ones would make git (and GitHub's
// attribution) read only the last paragraph as the trailer block and drop the existing co-authors.
const body = text.replace(/\s+$/, '');
fs.writeFileSync(message, (body ? body + '\n\n' : '') + appended.join('\n') + '\n');
const cut = body.lastIndexOf('\n\n');
const endsInTrailer = cut >= 0 && body.slice(cut + 2).split('\n').every(l => /^[A-Za-z][\w-]*: \S/.test(l));
fs.writeFileSync(message, (body ? body + (endsInTrailer ? '\n' : '\n\n') : '') + appended.join('\n') + '\n');
}
}
if (argv.includes('--json')) process.stdout.write(JSON.stringify({ range, authors: [...authors.values()], trailers, appended }) + '\n');
+6 -1
View File
@@ -72,8 +72,13 @@ test('--message appends only the missing trailers and is idempotent; a bad range
const file = path.join(dir, 'MSG');
fs.writeFileSync(file, 'port: contributor fix\n\nCo-authored-by: ada contributor <ADA@example.org>\n');
expect(run(dir, ['--message', file]).status).toBe(EXIT.ok);
const expected = 'port: contributor fix\n\nCo-authored-by: ada contributor <ADA@example.org>\n\nCo-authored-by: Bo Helper <bo@example.org>\n';
const expected = 'port: contributor fix\n\nCo-authored-by: ada contributor <ADA@example.org>\nCo-authored-by: Bo Helper <bo@example.org>\n';
expect(fs.readFileSync(file, 'utf8')).toBe(expected);
expect(spawnSync('git', ['interpret-trailers', '--parse'], { cwd: dir, input: expected, encoding: 'utf8' }).stdout.trim().split('\n')).toHaveLength(2);
const plain = path.join(dir, 'PLAIN');
fs.writeFileSync(plain, 'port: contributor fix\n');
expect(run(dir, ['--message', plain]).status).toBe(EXIT.ok);
expect(fs.readFileSync(plain, 'utf8')).toBe('port: contributor fix\n\nCo-authored-by: Ada Contributor <Ada@Example.org>\nCo-authored-by: Bo Helper <bo@example.org>\n');
const again = run(dir, ['--message', file, '--json']);
expect(JSON.parse(again.stdout).appended).toEqual([]);
expect(fs.readFileSync(file, 'utf8')).toBe(expected);