feat(dev-infra): support squashing fixups in the `ng-dev pr rebase` command (#39747)
Adding support for squashing fixup commits during when rebasing can allow for rebasing to better unblock things like merging. PR Close #39747
This commit is contained in:
parent
881b77ef46
commit
b1ebfb1cab
|
@ -1779,6 +1779,30 @@ function parseCommitMessage(commitMsg) {
|
||||||
isRevert: REVERT_PREFIX_RE.test(commitMsg),
|
isRevert: REVERT_PREFIX_RE.test(commitMsg),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/** Retrieve and parse each commit message in a provide range. */
|
||||||
|
function parseCommitMessagesForRange(range) {
|
||||||
|
/** A random number used as a split point in the git log result. */
|
||||||
|
const randomValueSeparator = `${Math.random()}`;
|
||||||
|
/**
|
||||||
|
* Custom git log format that provides the commit header and body, separated as expected with the
|
||||||
|
* custom separator as the trailing value.
|
||||||
|
*/
|
||||||
|
const gitLogFormat = `%s%n%n%b${randomValueSeparator}`;
|
||||||
|
// Retrieve the commits in the provided range.
|
||||||
|
const result = exec(`git log --reverse --format=${gitLogFormat} ${range}`);
|
||||||
|
if (result.code) {
|
||||||
|
throw new Error(`Failed to get all commits in the range:\n ${result.stderr}`);
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
// Separate the commits from a single string into individual commits.
|
||||||
|
.split(randomValueSeparator)
|
||||||
|
// Remove extra space before and after each commit message.
|
||||||
|
.map(l => l.trim())
|
||||||
|
// Remove any superfluous lines which remain from the split.
|
||||||
|
.filter(line => !!line)
|
||||||
|
// Parse each commit message.
|
||||||
|
.map(commit => parseCommitMessage(commit));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
|
@ -1980,38 +2004,6 @@ const ValidateFileModule = {
|
||||||
describe: 'Validate the most recent commit message',
|
describe: 'Validate the most recent commit message',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
/** Retrieve and parse each commit message in a provide range. */
|
|
||||||
function parseCommitMessagesForRange(range) {
|
|
||||||
/** A random number used as a split point in the git log result. */
|
|
||||||
const randomValueSeparator = `${Math.random()}`;
|
|
||||||
/**
|
|
||||||
* Custom git log format that provides the commit header and body, separated as expected with the
|
|
||||||
* custom separator as the trailing value.
|
|
||||||
*/
|
|
||||||
const gitLogFormat = `%s%n%n%b${randomValueSeparator}`;
|
|
||||||
// Retrieve the commits in the provided range.
|
|
||||||
const result = exec(`git log --reverse --format=${gitLogFormat} ${range}`);
|
|
||||||
if (result.code) {
|
|
||||||
throw new Error(`Failed to get all commits in the range:\n ${result.stderr}`);
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
// Separate the commits from a single string into individual commits.
|
|
||||||
.split(randomValueSeparator)
|
|
||||||
// Remove extra space before and after each commit message.
|
|
||||||
.map(l => l.trim())
|
|
||||||
// Remove any superfluous lines which remain from the split.
|
|
||||||
.filter(line => !!line)
|
|
||||||
// Parse each commit message.
|
|
||||||
.map(commit => parseCommitMessage(commit));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright Google LLC All Rights Reserved.
|
* Copyright Google LLC All Rights Reserved.
|
||||||
|
@ -4352,12 +4344,26 @@ function rebasePr(prNumber, githubToken, config = getConfig()) {
|
||||||
// Fetch the branch at the commit of the PR, and check it out in a detached state.
|
// Fetch the branch at the commit of the PR, and check it out in a detached state.
|
||||||
info(`Checking out PR #${prNumber} from ${fullHeadRef}`);
|
info(`Checking out PR #${prNumber} from ${fullHeadRef}`);
|
||||||
git.run(['fetch', '-q', headRefUrl, headRefName]);
|
git.run(['fetch', '-q', headRefUrl, headRefName]);
|
||||||
git.run(['checkout', '--detach', 'FETCH_HEAD']);
|
git.run(['checkout', '-q', '--detach', 'FETCH_HEAD']);
|
||||||
// Fetch the PRs target branch and rebase onto it.
|
// Fetch the PRs target branch and rebase onto it.
|
||||||
info(`Fetching ${fullBaseRef} to rebase #${prNumber} on`);
|
info(`Fetching ${fullBaseRef} to rebase #${prNumber} on`);
|
||||||
git.run(['fetch', '-q', baseRefUrl, baseRefName]);
|
git.run(['fetch', '-q', baseRefUrl, baseRefName]);
|
||||||
|
const commonAncestorSha = git.run(['merge-base', 'HEAD', 'FETCH_HEAD']).stdout.trim();
|
||||||
|
const commits = parseCommitMessagesForRange(`${commonAncestorSha}..HEAD`);
|
||||||
|
let squashFixups = commits.filter((commit) => commit.isFixup).length === 0 ?
|
||||||
|
false :
|
||||||
|
yield promptConfirm(`PR #${prNumber} contains fixup commits, would you like to squash them during rebase?`, true);
|
||||||
info(`Attempting to rebase PR #${prNumber} on ${fullBaseRef}`);
|
info(`Attempting to rebase PR #${prNumber} on ${fullBaseRef}`);
|
||||||
const rebaseResult = git.runGraceful(['rebase', 'FETCH_HEAD']);
|
/**
|
||||||
|
* Tuple of flags to be added to the rebase command and env object to run the git command.
|
||||||
|
*
|
||||||
|
* Additional flags to perform the autosquashing are added when the user confirm squashing of
|
||||||
|
* fixup commits should occur.
|
||||||
|
*/
|
||||||
|
const [flags, env] = squashFixups ?
|
||||||
|
[['--interactive', '--autosquash'], Object.assign(Object.assign({}, process.env), { GIT_SEQUENCE_EDITOR: 'true' })] :
|
||||||
|
[[], undefined];
|
||||||
|
const rebaseResult = git.runGraceful(['rebase', ...flags, 'FETCH_HEAD'], { env: env });
|
||||||
// If the rebase was clean, push the rebased PR up to the authors fork.
|
// If the rebase was clean, push the rebased PR up to the authors fork.
|
||||||
if (rebaseResult.status === 0) {
|
if (rebaseResult.status === 0) {
|
||||||
info(`Rebase was able to complete automatically without conflicts`);
|
info(`Rebase was able to complete automatically without conflicts`);
|
||||||
|
|
|
@ -9,6 +9,7 @@ ts_library(
|
||||||
module_name = "@angular/dev-infra-private/pr/rebase",
|
module_name = "@angular/dev-infra-private/pr/rebase",
|
||||||
visibility = ["//dev-infra:__subpackages__"],
|
visibility = ["//dev-infra:__subpackages__"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//dev-infra/commit-message",
|
||||||
"//dev-infra/utils",
|
"//dev-infra/utils",
|
||||||
"@npm//@types/inquirer",
|
"@npm//@types/inquirer",
|
||||||
"@npm//@types/node",
|
"@npm//@types/node",
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {types as graphQLTypes} from 'typed-graphqlify';
|
import {types as graphQLTypes} from 'typed-graphqlify';
|
||||||
|
import {parseCommitMessagesForRange, ParsedCommitMessage} from '../../commit-message/parse';
|
||||||
|
|
||||||
import {getConfig, NgDevConfig} from '../../utils/config';
|
import {getConfig, NgDevConfig} from '../../utils/config';
|
||||||
import {error, info, promptConfirm} from '../../utils/console';
|
import {error, info, promptConfirm} from '../../utils/console';
|
||||||
|
@ -85,13 +86,34 @@ export async function rebasePr(
|
||||||
// Fetch the branch at the commit of the PR, and check it out in a detached state.
|
// Fetch the branch at the commit of the PR, and check it out in a detached state.
|
||||||
info(`Checking out PR #${prNumber} from ${fullHeadRef}`);
|
info(`Checking out PR #${prNumber} from ${fullHeadRef}`);
|
||||||
git.run(['fetch', '-q', headRefUrl, headRefName]);
|
git.run(['fetch', '-q', headRefUrl, headRefName]);
|
||||||
git.run(['checkout', '--detach', 'FETCH_HEAD']);
|
git.run(['checkout', '-q', '--detach', 'FETCH_HEAD']);
|
||||||
|
|
||||||
// Fetch the PRs target branch and rebase onto it.
|
// Fetch the PRs target branch and rebase onto it.
|
||||||
info(`Fetching ${fullBaseRef} to rebase #${prNumber} on`);
|
info(`Fetching ${fullBaseRef} to rebase #${prNumber} on`);
|
||||||
git.run(['fetch', '-q', baseRefUrl, baseRefName]);
|
git.run(['fetch', '-q', baseRefUrl, baseRefName]);
|
||||||
|
|
||||||
|
const commonAncestorSha = git.run(['merge-base', 'HEAD', 'FETCH_HEAD']).stdout.trim();
|
||||||
|
|
||||||
|
const commits = parseCommitMessagesForRange(`${commonAncestorSha}..HEAD`);
|
||||||
|
|
||||||
|
let squashFixups =
|
||||||
|
commits.filter((commit: ParsedCommitMessage) => commit.isFixup).length === 0 ?
|
||||||
|
false :
|
||||||
|
await promptConfirm(
|
||||||
|
`PR #${prNumber} contains fixup commits, would you like to squash them during rebase?`,
|
||||||
|
true);
|
||||||
|
|
||||||
info(`Attempting to rebase PR #${prNumber} on ${fullBaseRef}`);
|
info(`Attempting to rebase PR #${prNumber} on ${fullBaseRef}`);
|
||||||
const rebaseResult = git.runGraceful(['rebase', 'FETCH_HEAD']);
|
|
||||||
|
/**
|
||||||
|
* Tuple of flags to be added to the rebase command and env object to run the git command.
|
||||||
|
*
|
||||||
|
* Additional flags to perform the autosquashing are added when the user confirm squashing of
|
||||||
|
* fixup commits should occur.
|
||||||
|
*/
|
||||||
|
const [flags, env] = squashFixups ?
|
||||||
|
[['--interactive', '--autosquash'], {...process.env, GIT_SEQUENCE_EDITOR: 'true'}] :
|
||||||
|
[[], undefined];
|
||||||
|
const rebaseResult = git.runGraceful(['rebase', ...flags, 'FETCH_HEAD'], {env: env});
|
||||||
|
|
||||||
// If the rebase was clean, push the rebased PR up to the authors fork.
|
// If the rebase was clean, push the rebased PR up to the authors fork.
|
||||||
if (rebaseResult.status === 0) {
|
if (rebaseResult.status === 0) {
|
||||||
|
|
Loading…
Reference in New Issue