Skip to content

Commit f131b09

Browse files
authored
fix(git-node): handle multi-line trailers (#1062)
1 parent f0587fa commit f131b09

2 files changed

Lines changed: 56 additions & 27 deletions

File tree

lib/landing_session.js

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -316,40 +316,49 @@ export default class LandingSession extends Session {
316316

317317
// git has very specific rules about what is a trailer and what is not.
318318
// Instead of trying to implement those ourselves, let git parse the
319-
// original commit message and see if it outputs any trailers.
320-
const originalTrailers = runSync('git', [
319+
// commit message and see if it outputs any trailers.
320+
const interpretTrailers = commitMessage => runSync('git', [
321321
'interpret-trailers', '--parse', '--no-divider'
322322
], {
323-
input: `${original}\n`
324-
}).split('\n').map(trailer => {
325-
const separatorIndex = trailer.indexOf(':');
326-
return [trailer.slice(0, separatorIndex), trailer.slice(separatorIndex + 2)];
327-
}).slice(0, -1); // Remove the last line return entry
323+
input: `${commitMessage}\n`
324+
});
328325

326+
const originalTrailers = interpretTrailers(original);
329327
const containCVETrailer = CVE_RE.test(originalTrailers);
330328

331-
const filterTrailer = (line) => ([key]) =>
332-
line.startsWith(key) &&
333-
new RegExp(`^${RegExp.escape?.(key) ?? key}\\s*:`).test(line);
334-
const amended = original.trim().split('\n')
335-
.filter(line =>
336-
!line.includes(':') ||
337-
!originalTrailers.some(filterTrailer(line)));
338-
for (let i = amended.length - 1; amended[i] === ''; i--) {
339-
amended.pop();
340-
}
329+
const amended = original.trim().split('\n');
330+
let keptTrailers = [];
331+
if (originalTrailers) {
332+
const stillInTrailers = () => {
333+
const result = interpretTrailers(amended.join('\n'));
334+
return result.length && originalTrailers.startsWith(result.trim());
335+
};
336+
// Remove all lines until we find an empty string, which should get of all
337+
// trailers in most cases.
338+
amended.splice(amended.lastIndexOf(''), Infinity);
339+
for (let i = amended.length - 1; amended[i] === '' || stillInTrailers(); i--) {
340+
// Remove last line until git no longer detects any trailers
341+
amended.pop();
342+
}
341343

342-
// Only keep existing trailers that we won't add ourselves
343-
const trailersToFilterOut = ['BACKPORT-PR-URL', 'REVIEWED-BY'];
344-
if (!this.backport) trailersToFilterOut.push('PR-URL');
345-
const keptTrailers = originalTrailers.filter(
346-
([key]) => !trailersToFilterOut.includes(key.toUpperCase())
347-
);
344+
// Only keep existing trailers that we won't add ourselves
345+
const trailersToFilterOut = ['BACKPORT-PR-URL', 'REVIEWED-BY'];
346+
if (!this.backport) trailersToFilterOut.push('PR-URL');
347+
keptTrailers = originalTrailers
348+
.split('\n')
349+
.map(trailer => {
350+
const separatorIndex = trailer.indexOf(':');
351+
return [trailer.slice(0, separatorIndex), trailer.slice(separatorIndex + 2)];
352+
})
353+
.slice(0, -1) // Remove the last line return entry
354+
.filter(
355+
([key]) => !trailersToFilterOut.includes(key.toUpperCase())
356+
);
357+
}
348358
amended.push('', ...keptTrailers.map(([key, value]) => `${key}: ${value}`));
349359

350360
for (const line of metadata.trim().split('\n')) {
351-
const foundMatchingTrailer = keptTrailers.find(filterTrailer(line));
352-
if (foundMatchingTrailer && line === foundMatchingTrailer.join(': ')) {
361+
if (keptTrailers.some((trailer) => line.toUpperCase() === trailer.join(': ').toUpperCase())) {
353362
cli.warn(`Found ${line}, skipping..`);
354363
continue;
355364
}

test/unit/amend.test.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,31 @@ describe('LandingSession.prototype.generateAmendedMessage', () => {
105105
t.assert.strictEqual(result, 'subsystem: foobar\n\nTrailer: Value\nPR-URL: http://example.com/123\nReviewed-By: user1 <[email protected]>');
106106
});
107107

108-
it('should handle empty message', async(t) => {
108+
it('should handle empty message (although not supported)', async(t) => {
109109
const session = createSession();
110110
const result = await session.generateAmendedMessage('');
111111

112-
t.assert.strictEqual(result, '\nPR-URL: http://example.com/123\nReviewed-By: user1 <[email protected]>');
112+
t.assert.strictEqual(result, '\n\nPR-URL: http://example.com/123\nReviewed-By: user1 <[email protected]>');
113+
});
114+
115+
it('should handle multi-line trailers', async(t) => {
116+
const session = createSession();
117+
const result = await session.generateAmendedMessage(
118+
'subsystem: foobar\n\nSigned-off-by: user1\n <[email protected]>\n'
119+
);
120+
121+
t.assert.strictEqual(result,
122+
'subsystem: foobar\n\nSigned-off-by: user1 <[email protected]>\nPR-URL: http://example.com/123\nReviewed-By: user1 <[email protected]>');
123+
});
124+
125+
it('should not remove lines that look like trailers in the commit body', async(t) => {
126+
const session = createSession();
127+
const result = await session.generateAmendedMessage(
128+
'subsystem: foobar\n\nNot-A-Trailer: http://example.com/\n\nSigned-off-by: user1\n <[email protected]>\n'
129+
);
130+
131+
t.assert.strictEqual(result,
132+
'subsystem: foobar\n\nNot-A-Trailer: http://example.com/\n\nSigned-off-by: user1 <[email protected]>\nPR-URL: http://example.com/123\nReviewed-By: user1 <[email protected]>');
113133
});
114134

115135
it('should handle cherry-pick from upstream', async(t) => {

0 commit comments

Comments
 (0)