feat(dev-infra): close pull requests on merge which do not target master branch (#39979)

Previously, when a PR which does not target the master branch in the Github UI was
merged it would not close automatically.  This change detects when this case occurs
and closes the PR via the Github API.

For example:

  A PR which targets the 11.0.x branch in the Github UI has the `target: patch` label

This PR is only pushed into the 11.0.x branch, which does not trigger Github's
reference based actions to close the PR.

PR Close #39979
This commit is contained in:
Joey Perrott 2020-12-04 15:16:14 -08:00 committed by Misko Hevery
parent a8aeb74714
commit d9356f2842
2 changed files with 70 additions and 28 deletions

View File

@ -3900,35 +3900,52 @@ var AutosquashMergeStrategy = /** @class */ (function (_super) {
*/
AutosquashMergeStrategy.prototype.merge = function (pullRequest) {
return tslib.__awaiter(this, void 0, void 0, function () {
var prNumber, targetBranches, requiredBaseSha, needsCommitMessageFixup, baseSha, revisionRange, branchOrRevisionBeforeRebase, rebaseEnv, failedBranches;
var prNumber, targetBranches, requiredBaseSha, needsCommitMessageFixup, githubTargetBranch, baseSha, revisionRange, branchOrRevisionBeforeRebase, rebaseEnv, failedBranches, localBranch, sha;
return tslib.__generator(this, function (_a) {
prNumber = pullRequest.prNumber, targetBranches = pullRequest.targetBranches, requiredBaseSha = pullRequest.requiredBaseSha, needsCommitMessageFixup = pullRequest.needsCommitMessageFixup;
// In case a required base is specified for this pull request, check if the pull
// request contains the given commit. If not, return a pull request failure. This
// check is useful for enforcing that PRs are rebased on top of a given commit. e.g.
// a commit that changes the codeowner ship validation. PRs which are not rebased
// could bypass new codeowner ship rules.
if (requiredBaseSha && !this.git.hasCommit(TEMP_PR_HEAD_BRANCH, requiredBaseSha)) {
return [2 /*return*/, PullRequestFailure.unsatisfiedBaseSha()];
switch (_a.label) {
case 0:
prNumber = pullRequest.prNumber, targetBranches = pullRequest.targetBranches, requiredBaseSha = pullRequest.requiredBaseSha, needsCommitMessageFixup = pullRequest.needsCommitMessageFixup, githubTargetBranch = pullRequest.githubTargetBranch;
// In case a required base is specified for this pull request, check if the pull
// request contains the given commit. If not, return a pull request failure. This
// check is useful for enforcing that PRs are rebased on top of a given commit. e.g.
// a commit that changes the codeowner ship validation. PRs which are not rebased
// could bypass new codeowner ship rules.
if (requiredBaseSha && !this.git.hasCommit(TEMP_PR_HEAD_BRANCH, requiredBaseSha)) {
return [2 /*return*/, PullRequestFailure.unsatisfiedBaseSha()];
}
baseSha = this.git.run(['rev-parse', this.getPullRequestBaseRevision(pullRequest)]).stdout.trim();
revisionRange = baseSha + ".." + TEMP_PR_HEAD_BRANCH;
branchOrRevisionBeforeRebase = this.git.getCurrentBranchOrRevision();
rebaseEnv = needsCommitMessageFixup ? undefined : tslib.__assign(tslib.__assign({}, process.env), { GIT_SEQUENCE_EDITOR: 'true' });
this.git.run(['rebase', '--interactive', '--autosquash', baseSha, TEMP_PR_HEAD_BRANCH], { stdio: 'inherit', env: rebaseEnv });
// Update pull requests commits to reference the pull request. This matches what
// Github does when pull requests are merged through the Web UI. The motivation is
// that it should be easy to determine which pull request contained a given commit.
// Note: The filter-branch command relies on the working tree, so we want to make sure
// that we are on the initial branch or revision where the merge script has been invoked.
this.git.run(['checkout', '-f', branchOrRevisionBeforeRebase]);
this.git.run(['filter-branch', '-f', '--msg-filter', MSG_FILTER_SCRIPT + " " + prNumber, revisionRange]);
failedBranches = this.cherryPickIntoTargetBranches(revisionRange, targetBranches);
if (failedBranches.length) {
return [2 /*return*/, PullRequestFailure.mergeConflicts(failedBranches)];
}
this.pushTargetBranchesUpstream(targetBranches);
if (!(githubTargetBranch !== 'master')) return [3 /*break*/, 3];
localBranch = this.getLocalTargetBranchName(githubTargetBranch);
sha = this.git.run(['rev-parse', localBranch]).stdout.trim();
// Create a comment saying the PR was closed by the SHA.
return [4 /*yield*/, this.git.github.issues.createComment(tslib.__assign(tslib.__assign({}, this.git.remoteParams), { issue_number: pullRequest.prNumber, body: "Closed by commit " + sha }))];
case 1:
// Create a comment saying the PR was closed by the SHA.
_a.sent();
// Actually close the PR.
return [4 /*yield*/, this.git.github.pulls.update(tslib.__assign(tslib.__assign({}, this.git.remoteParams), { pull_number: pullRequest.prNumber, state: 'closed' }))];
case 2:
// Actually close the PR.
_a.sent();
_a.label = 3;
case 3: return [2 /*return*/, null];
}
baseSha = this.git.run(['rev-parse', this.getPullRequestBaseRevision(pullRequest)]).stdout.trim();
revisionRange = baseSha + ".." + TEMP_PR_HEAD_BRANCH;
branchOrRevisionBeforeRebase = this.git.getCurrentBranchOrRevision();
rebaseEnv = needsCommitMessageFixup ? undefined : tslib.__assign(tslib.__assign({}, process.env), { GIT_SEQUENCE_EDITOR: 'true' });
this.git.run(['rebase', '--interactive', '--autosquash', baseSha, TEMP_PR_HEAD_BRANCH], { stdio: 'inherit', env: rebaseEnv });
// Update pull requests commits to reference the pull request. This matches what
// Github does when pull requests are merged through the Web UI. The motivation is
// that it should be easy to determine which pull request contained a given commit.
// Note: The filter-branch command relies on the working tree, so we want to make sure
// that we are on the initial branch or revision where the merge script has been invoked.
this.git.run(['checkout', '-f', branchOrRevisionBeforeRebase]);
this.git.run(['filter-branch', '-f', '--msg-filter', MSG_FILTER_SCRIPT + " " + prNumber, revisionRange]);
failedBranches = this.cherryPickIntoTargetBranches(revisionRange, targetBranches);
if (failedBranches.length) {
return [2 /*return*/, PullRequestFailure.mergeConflicts(failedBranches)];
}
this.pushTargetBranchesUpstream(targetBranches);
return [2 /*return*/, null];
});
});
};

View File

@ -32,7 +32,8 @@ export class AutosquashMergeStrategy extends MergeStrategy {
* @returns A pull request failure or null in case of success.
*/
async merge(pullRequest: PullRequest): Promise<PullRequestFailure|null> {
const {prNumber, targetBranches, requiredBaseSha, needsCommitMessageFixup} = pullRequest;
const {prNumber, targetBranches, requiredBaseSha, needsCommitMessageFixup, githubTargetBranch} =
pullRequest;
// In case a required base is specified for this pull request, check if the pull
// request contains the given commit. If not, return a pull request failure. This
// check is useful for enforcing that PRs are rebased on top of a given commit. e.g.
@ -83,6 +84,30 @@ export class AutosquashMergeStrategy extends MergeStrategy {
}
this.pushTargetBranchesUpstream(targetBranches);
// For PRs which do not target the `master` branch on Github, Github does not automatically
// close the PR when its commit is pushed into the repository. To ensure these PRs are
// correctly marked as closed, we must detect this situation and close the PR via the API after
// the upstream pushes are completed.
if (githubTargetBranch !== 'master') {
/** The local branch name of the github targeted branch. */
const localBranch = this.getLocalTargetBranchName(githubTargetBranch);
/** The SHA of the commit pushed to github which represents closing the PR. */
const sha = this.git.run(['rev-parse', localBranch]).stdout.trim();
// Create a comment saying the PR was closed by the SHA.
await this.git.github.issues.createComment({
...this.git.remoteParams,
issue_number: pullRequest.prNumber,
body: `Closed by commit ${sha}`
});
// Actually close the PR.
await this.git.github.pulls.update({
...this.git.remoteParams,
pull_number: pullRequest.prNumber,
state: 'closed',
});
}
return null;
}
}