Files
renovate/lib/util/git/commit-trailers.ts
Felipe Santos d32e38ec2b fix(github): include commitTrailers in platform-native commits (#44918)
* fix(github): include commitTrailers in platform-native commits

The GitHub platform-native commit path (platformCommit) recreates the
commit via the REST API using only message, so trailers from
commitTrailers / :gitSignOff were dropped even though prepareCommit
wrote them into the local commit.

* test: use codeBlock for multi-line commit trailer expectations

* Remove unnecessary comment

* refactor(gerrit): format commit message with shared helper

Use formatCommitMessage for Gerrit reserved trailers so the final
message is built the same way as the git and GitHub platform-native
paths, then clear trailers so prepareCommit does not append them twice.

* Revert "refactor(gerrit): format commit message with shared helper"

This reverts commit d230fa3802.

* refactor(git): fold commit trailers into the message before scm

Remove CommitFilesConfig.trailers so platforms only ever see a fully
formatted message. Workers call formatCommitMessage once; prepareCommit
and GitHub platform-native commits pass message through as-is. Gerrit
parses the existing trailer block to merge Renovate-Branch/Change-Id.

* refactor(git): drop formatCommitMessage helper

Trailers are already folded into message at the worker boundary, so the
shared formatter only re-joined strings. Inline that at the few call sites
and keep splitCommitMessage for Gerrit's reserved-trailer merge.

* revert: keep the minimal commitTrailers platform-native fix

Drop the later refactors (fold trailers into message, Gerrit changes,
splitCommitMessage). Keep formatCommitMessage shared by prepareCommit
and GitHub platform-native commits only; Gerrit is unchanged.
2026-07-29 06:16:44 +00:00

52 lines
1.5 KiB
TypeScript

import { isNonEmptyArray, isString } from '@sindresorhus/is';
import { logger } from '../../logger/index.ts';
import { regEx } from '../regex.ts';
const commitTrailerRe = regEx(/^[A-Za-z0-9-]+: [^\r\n]+$/);
/**
* A valid git trailer is a single-line `Key: value` entry where the key
* contains only letters, digits and `-`, and the value is non-empty.
*/
export function isValidCommitTrailer(trailer: unknown): trailer is string {
return isString(trailer) && commitTrailerRe.test(trailer);
}
/**
* Keep only trailers that are valid single-line `Key: value` entries.
*/
export function filterValidCommitTrailers(trailers: string[]): string[] {
const valid: string[] = [];
const invalid: string[] = [];
for (const trailer of trailers) {
if (isValidCommitTrailer(trailer)) {
valid.push(trailer);
} else {
invalid.push(trailer);
}
}
if (isNonEmptyArray(invalid)) {
logger.warn(
{ invalid },
'Ignoring invalid commit trailers (must be a single-line Key: value)',
);
}
return valid;
}
/**
* Build the full commit message with trailers as the final block.
* Paragraphs (message parts and the trailer block) are separated by blank
* lines, matching `git commit -m ... -m ...` behavior.
*/
export function formatCommitMessage(
message: string | string[],
trailers?: string[],
): string {
const parts = isString(message) ? [message] : [...message];
if (isNonEmptyArray(trailers)) {
parts.push(trailers.join('\n'));
}
return parts.join('\n\n');
}