Skip to content

Commit e0d6741

Browse files
committed
fixup! feat(ng-dev): create pr takeover command
1 parent 906dfac commit e0d6741

File tree

11 files changed

+128
-158
lines changed

11 files changed

+128
-158
lines changed

ng-dev/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ esbuild_esm_bundle(
5858
# to launch these files/scripts dynamically (through e.g. `spawn` or `fork`).
5959
"//ng-dev/release/build:build-worker.ts",
6060
"//ng-dev/pr/merge:strategies/commit-message-filter.ts",
61-
"//ng-dev/pr/takeover-pr:commit-message-filter.ts",
61+
"//ng-dev/pr/checkout:commit-message-filter.ts",
6262
],
6363
external = NG_DEV_EXTERNALS,
6464
splitting = True,

ng-dev/pr/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ ts_library(
1010
"//ng-dev/pr/discover-new-conflicts",
1111
"//ng-dev/pr/merge",
1212
"//ng-dev/pr/rebase",
13-
"//ng-dev/pr/takeover-pr",
1413
"@npm//@types/yargs",
1514
],
1615
)

ng-dev/pr/checkout/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
load("//tools:defaults.bzl", "ts_library")
22

3+
exports_files([
4+
"commit-message-filter.ts",
5+
])
6+
37
ts_library(
48
name = "checkout",
59
srcs = glob(["*.ts"]),
610
visibility = ["//ng-dev:__subpackages__"],
711
deps = [
812
"//ng-dev/pr/common",
913
"//ng-dev/utils",
14+
"@npm//@types/node",
1015
"@npm//@types/yargs",
1116
],
1217
)

ng-dev/pr/checkout/checkout.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {dirname, join} from 'path';
2+
import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client.js';
3+
import {Prompt} from '../../utils/prompt.js';
4+
import {Log, bold, green} from '../../utils/logging.js';
5+
import {checkOutPullRequestLocally} from '../common/checkout-pr.js';
6+
import {fileURLToPath} from 'url';
7+
8+
/** List of accounts that are supported for takeover. */
9+
const takeoverAccounts = ['angular-robot'];
10+
11+
export interface CheckoutPullRequestParams {
12+
pr: number;
13+
takeover?: boolean;
14+
}
15+
16+
export async function checkoutPullRequest(params: CheckoutPullRequestParams): Promise<void> {
17+
const {pr, takeover} = params;
18+
/** An authenticated git client. */
19+
const git = await AuthenticatedGitClient.get();
20+
/** The branch name used for the takeover change. */
21+
const branchName = `pr-takeover-${pr}`;
22+
23+
// Make sure the local repository is clean.
24+
if (git.hasUncommittedChanges()) {
25+
Log.error(
26+
` ✘ Local working repository not clean. Please make sure there are no uncommitted changes`,
27+
);
28+
return;
29+
}
30+
31+
const {resetGitState, pullRequest, pushToUpstreamCommand} = await checkOutPullRequestLocally(pr, {
32+
allowIfMaintainerCannotModify: true,
33+
});
34+
35+
// if maintainer can modify is false or if takeover is provided do takeover
36+
37+
if (pullRequest.maintainerCanModify === false || takeover) {
38+
if (takeover !== true) {
39+
Log.info('The author of this pull request does not allow maintainers to modify the pull');
40+
Log.info('request. Since you will not be able to push changes to the original pull request');
41+
Log.info('you will instead need to perform a "takeover." In a takeover the original pull');
42+
Log.info('request will be checked out, the commits are modified to close the original on');
43+
Log.info('merge of the newly created branch.\n');
44+
45+
if (!(await Prompt.confirm(`Would you like to create a takeover pull request?`, true))) {
46+
Log.info('Aborting takeover..');
47+
await resetGitState();
48+
return;
49+
}
50+
}
51+
52+
if (git.runGraceful(['rev-parse', '-q', '--verify', branchName]).status === 0) {
53+
Log.error(` ✘ Expected branch name \`${branchName}\` already exists locally`);
54+
return;
55+
}
56+
57+
// Confirm that the takeover request is being done on a valid pull request.
58+
if (!takeoverAccounts.includes(pullRequest.author.login)) {
59+
Log.warn(
60+
` ⚠ ${bold(pullRequest.author.login)} is not an account fully supported for takeover.`,
61+
);
62+
Log.warn(` Supported accounts: ${bold(takeoverAccounts.join(', '))}`);
63+
if (await Prompt.confirm(`Continue with pull request takeover anyway?`, true)) {
64+
Log.debug('Continuing per user confirmation in prompt');
65+
} else {
66+
Log.info('Aborting takeover..');
67+
await resetGitState();
68+
return;
69+
}
70+
}
71+
72+
Log.info(`Setting local branch name based on the pull request`);
73+
git.run(['checkout', '-q', '-b', branchName]);
74+
75+
Log.info('Updating commit messages to close previous pull request');
76+
git.run([
77+
'filter-branch',
78+
'-f',
79+
'--msg-filter',
80+
`${getCommitMessageFilterScriptPath()} ${pr}`,
81+
`${pullRequest.baseRefOid}..HEAD`,
82+
]);
83+
84+
Log.info(` ${green('✔')} Checked out pull request #${pr} into branch: ${branchName}`);
85+
return;
86+
}
87+
88+
Log.info(`Checked out the remote branch for pull request #${pr}\n`);
89+
Log.info('To push the checked out branch back to its PR, run the following command:');
90+
Log.info(` $ ${pushToUpstreamCommand}`);
91+
}
92+
93+
/** Gets the absolute file path to the commit-message filter script. */
94+
function getCommitMessageFilterScriptPath(): string {
95+
// This file is getting bundled and ends up in `<pkg-root>/bundles/<chunk>`. We also
96+
// bundle the commit-message-filter script as another entry-point and can reference
97+
// it relatively as the path is preserved inside `bundles/`.
98+
// *Note*: Relying on package resolution is problematic within ESM and with `local-dev.sh`
99+
const bundlesDir = dirname(fileURLToPath(import.meta.url));
100+
return join(bundlesDir, './pr/checkout/commit-message-filter.mjs');
101+
}

ng-dev/pr/checkout/cli.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,32 @@
77
*/
88

99
import {Argv, Arguments, CommandModule} from 'yargs';
10-
import {Log} from '../../utils/logging.js';
1110

1211
import {addGithubTokenOption} from '../../utils/git/github-yargs.js';
13-
import {checkOutPullRequestLocally} from '../common/checkout-pr.js';
14-
15-
export interface CheckoutOptions {
16-
pr: number;
17-
}
12+
import {checkoutPullRequest, CheckoutPullRequestParams} from './checkout.js';
1813

1914
/** Builds the checkout pull request command. */
2015
function builder(yargs: Argv) {
21-
return addGithubTokenOption(yargs).positional('pr', {type: 'number', demandOption: true});
16+
return addGithubTokenOption(yargs)
17+
.positional('pr', {
18+
type: 'number',
19+
demandOption: true,
20+
describe: 'The pull request number for the pull request to checkout',
21+
})
22+
.option('takeover', {
23+
type: 'boolean',
24+
demandOption: false,
25+
describe: 'Check out the pull request to perform a takeover',
26+
});
2227
}
2328

2429
/** Handles the checkout pull request command. */
25-
async function handler({pr}: Arguments<CheckoutOptions>) {
26-
const options = {allowIfMaintainerCannotModify: true, branchName: `pr-${pr}`};
27-
const {pushToUpstreamCommand} = await checkOutPullRequestLocally(pr, options);
28-
Log.info(`Checked out the remote branch for pull request #${pr}\n`);
29-
Log.info('To push the checked out branch back to its PR, run the following command:');
30-
Log.info(` $ ${pushToUpstreamCommand}`);
30+
async function handler({pr, takeover}: Arguments<CheckoutPullRequestParams>) {
31+
await checkoutPullRequest({pr, takeover});
3132
}
3233

3334
/** yargs command module for checking out a PR */
34-
export const CheckoutCommandModule: CommandModule<{}, CheckoutOptions> = {
35+
export const CheckoutCommandModule: CommandModule<{}, CheckoutPullRequestParams> = {
3536
handler,
3637
builder,
3738
command: 'checkout <pr>',

ng-dev/pr/takeover-pr/commit-message-filter.ts renamed to ng-dev/pr/checkout/commit-message-filter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,6 @@ function main() {
3939

4040
function rewriteCommitMessage(message: string, prNumber: string) {
4141
const lines = message.split(/\n/);
42-
lines.push(`Closes #${prNumber} as this was created using the \`ng-dev pr takeover\` tooling`);
42+
lines.push(`Closes #${prNumber} as a pr takeover`);
4343
return lines.join('\n');
4444
}

ng-dev/pr/cli.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {CheckoutCommandModule} from './checkout/cli.js';
1313
import {DiscoverNewConflictsCommandModule} from './discover-new-conflicts/cli.js';
1414
import {MergeCommandModule} from './merge/cli.js';
1515
import {RebaseCommandModule} from './rebase/cli.js';
16-
import {TakeoverPrCommandModule} from './takeover-pr/cli.js';
1716

1817
/** Build the parser for pull request commands. */
1918
export function buildPrParser(localYargs: Argv) {
@@ -25,6 +24,5 @@ export function buildPrParser(localYargs: Argv) {
2524
.command(RebaseCommandModule)
2625
.command(MergeCommandModule)
2726
.command(CheckoutCommandModule)
28-
.command(TakeoverPrCommandModule)
2927
.command(CheckTargetBranchesModule);
3028
}

ng-dev/pr/common/checkout-pr.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88

99
import {types as graphqlTypes} from 'typed-graphqlify';
1010

11-
import {Log} from '../../utils/logging.js';
1211
import {AuthenticatedGitClient} from '../../utils/git/authenticated-git-client.js';
1312
import {addTokenToGitHttpsUrl} from '../../utils/git/github-urls.js';
1413
import {getPr} from '../../utils/github.js';
1514

1615
/* Graphql schema for the response body for a pending PR. */
1716
const PR_SCHEMA = {
17+
author: {
18+
login: graphqlTypes.string,
19+
},
1820
state: graphqlTypes.string,
1921
maintainerCanModify: graphqlTypes.boolean,
2022
viewerDidAuthor: graphqlTypes.boolean,
@@ -26,6 +28,7 @@ const PR_SCHEMA = {
2628
nameWithOwner: graphqlTypes.string,
2729
},
2830
},
31+
baseRefOid: graphqlTypes.string,
2932
baseRef: {
3033
name: graphqlTypes.string,
3134
repository: {
@@ -107,7 +110,6 @@ export async function checkOutPullRequestLocally(
107110

108111
try {
109112
// Fetch the branch at the commit of the PR, and check it out in a detached state.
110-
Log.info(`Checking out PR #${prNumber} from ${fullHeadRef}`);
111113
git.run(['fetch', '-q', headRefUrl, headRefName]);
112114
git.run(['checkout', '--detach', 'FETCH_HEAD']);
113115
} catch (e) {
@@ -132,5 +134,6 @@ export async function checkOutPullRequestLocally(
132134
},
133135
pushToUpstreamCommand: `git push ${pr.headRef.repository.url} HEAD:${headRefName} ${forceWithLeaseFlag}`,
134136
resetGitStateCommand: `git rebase --abort && git reset --hard && git checkout ${previousBranchOrRevision}`,
137+
pullRequest: pr,
135138
};
136139
}

ng-dev/pr/takeover-pr/BUILD.bazel

Lines changed: 0 additions & 18 deletions
This file was deleted.

ng-dev/pr/takeover-pr/cli.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

ng-dev/pr/takeover-pr/takeover-pr.ts

Lines changed: 0 additions & 78 deletions
This file was deleted.

0 commit comments

Comments
 (0)