diff --git a/lib/landing_session.js b/lib/landing_session.js index 89d6e400..4bf1d6a3 100644 --- a/lib/landing_session.js +++ b/lib/landing_session.js @@ -316,40 +316,49 @@ export default class LandingSession extends Session { // git has very specific rules about what is a trailer and what is not. // Instead of trying to implement those ourselves, let git parse the - // original commit message and see if it outputs any trailers. - const originalTrailers = runSync('git', [ + // commit message and see if it outputs any trailers. + const interpretTrailers = commitMessage => runSync('git', [ 'interpret-trailers', '--parse', '--no-divider' ], { - input: `${original}\n` - }).split('\n').map(trailer => { - const separatorIndex = trailer.indexOf(':'); - return [trailer.slice(0, separatorIndex), trailer.slice(separatorIndex + 2)]; - }).slice(0, -1); // Remove the last line return entry + input: `${commitMessage}\n` + }); + const originalTrailers = interpretTrailers(original); const containCVETrailer = CVE_RE.test(originalTrailers); - const filterTrailer = (line) => ([key]) => - line.startsWith(key) && - new RegExp(`^${RegExp.escape?.(key) ?? key}\\s*:`).test(line); - const amended = original.trim().split('\n') - .filter(line => - !line.includes(':') || - !originalTrailers.some(filterTrailer(line))); - for (let i = amended.length - 1; amended[i] === ''; i--) { - amended.pop(); - } + const amended = original.trim().split('\n'); + let keptTrailers = []; + if (originalTrailers) { + const stillInTrailers = () => { + const result = interpretTrailers(amended.join('\n')); + return result.length && originalTrailers.startsWith(result.trim()); + }; + // Remove all lines until we find an empty string, which should get of all + // trailers in most cases. + amended.splice(amended.lastIndexOf(''), Infinity); + for (let i = amended.length - 1; amended[i] === '' || stillInTrailers(); i--) { + // Remove last line until git no longer detects any trailers + amended.pop(); + } - // Only keep existing trailers that we won't add ourselves - const trailersToFilterOut = ['BACKPORT-PR-URL', 'REVIEWED-BY']; - if (!this.backport) trailersToFilterOut.push('PR-URL'); - const keptTrailers = originalTrailers.filter( - ([key]) => !trailersToFilterOut.includes(key.toUpperCase()) - ); + // Only keep existing trailers that we won't add ourselves + const trailersToFilterOut = ['BACKPORT-PR-URL', 'REVIEWED-BY']; + if (!this.backport) trailersToFilterOut.push('PR-URL'); + keptTrailers = originalTrailers + .split('\n') + .map(trailer => { + const separatorIndex = trailer.indexOf(':'); + return [trailer.slice(0, separatorIndex), trailer.slice(separatorIndex + 2)]; + }) + .slice(0, -1) // Remove the last line return entry + .filter( + ([key]) => !trailersToFilterOut.includes(key.toUpperCase()) + ); + } amended.push('', ...keptTrailers.map(([key, value]) => `${key}: ${value}`)); for (const line of metadata.trim().split('\n')) { - const foundMatchingTrailer = keptTrailers.find(filterTrailer(line)); - if (foundMatchingTrailer && line === foundMatchingTrailer.join(': ')) { + if (keptTrailers.some((trailer) => line.toUpperCase() === trailer.join(': ').toUpperCase())) { cli.warn(`Found ${line}, skipping..`); continue; } diff --git a/test/unit/amend.test.js b/test/unit/amend.test.js index c205c8df..4d1591a2 100644 --- a/test/unit/amend.test.js +++ b/test/unit/amend.test.js @@ -105,11 +105,31 @@ describe('LandingSession.prototype.generateAmendedMessage', () => { t.assert.strictEqual(result, 'subsystem: foobar\n\nTrailer: Value\nPR-URL: http://example.com/123\nReviewed-By: user1 '); }); - it('should handle empty message', async(t) => { + it('should handle empty message (although not supported)', async(t) => { const session = createSession(); const result = await session.generateAmendedMessage(''); - t.assert.strictEqual(result, '\nPR-URL: http://example.com/123\nReviewed-By: user1 '); + t.assert.strictEqual(result, '\n\nPR-URL: http://example.com/123\nReviewed-By: user1 '); + }); + + it('should handle multi-line trailers', async(t) => { + const session = createSession(); + const result = await session.generateAmendedMessage( + 'subsystem: foobar\n\nSigned-off-by: user1\n \n' + ); + + t.assert.strictEqual(result, + 'subsystem: foobar\n\nSigned-off-by: user1 \nPR-URL: http://example.com/123\nReviewed-By: user1 '); + }); + + it('should not remove lines that look like trailers in the commit body', async(t) => { + const session = createSession(); + const result = await session.generateAmendedMessage( + 'subsystem: foobar\n\nNot-A-Trailer: http://example.com/\n\nSigned-off-by: user1\n \n' + ); + + t.assert.strictEqual(result, + 'subsystem: foobar\n\nNot-A-Trailer: http://example.com/\n\nSigned-off-by: user1 \nPR-URL: http://example.com/123\nReviewed-By: user1 '); }); it('should handle cherry-pick from upstream', async(t) => {