Skip to content

Commit 3fd512c

Browse files
committed
fix(github-actions): resolve pending mergeability checks on LTS branches
The branch-manager local GitHub Action previously relied on a mocked minimal configuration which lacked the "release" configuration block. As a result, validations checking the `TARGET_LTS` target label failed because they could not determine active LTS branches (which requires release config settings). This caused LTS PRs to throw an `InvalidTargetLabelError` and remain in a "pending" status permanently. This change fixes the issue by dynamically importing the actual repository's `.ng-dev/config.mjs` configuration after the repository is cloned inside the local action workspace, merging it with action input overrides and re-caching it. We also update `setCachedConfig` to support clearing/overwriting the cache by allowing `null` values.
1 parent 9ee8a68 commit 3fd512c

3 files changed

Lines changed: 500 additions & 483 deletions

File tree

.github/local-actions/branch-manager/lib/main.ts

Lines changed: 76 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -12,73 +12,77 @@ import {
1212
import {MergeConflictsFatalError} from '../../../../ng-dev/pr/merge/failures.js';
1313
import {createPullRequestValidationConfig} from '../../../../ng-dev/pr/common/validation/validation-config.js';
1414
import {InvalidTargetLabelError} from '../../../../ng-dev/pr/common/targeting/target-label.js';
15-
import {resolve} from 'path';
15+
import {
16+
assertValidCaretakerConfig,
17+
assertValidGithubConfig,
18+
setConfig,
19+
getConfig,
20+
} from '../../../../ng-dev/utils/config.js';
21+
import {assertValidPullRequestConfig} from '../../../../ng-dev/pr/config/index.js';
22+
import {setTimeout} from 'node:timers/promises';
23+
import {AuthenticatedGitClient} from '../../../../ng-dev/utils/git/authenticated-git-client.js';
1624

1725
interface CommmitStatus {
1826
state: 'pending' | 'error' | 'failure' | 'success';
1927
description: string;
2028
targetUrl?: string;
2129
}
2230

23-
/** The context name used for the commmit status applied. */
24-
const statusContextName = 'mergeability';
25-
/** The repository name for the pull request. */
26-
const repo = core.getInput('repo', {required: true, trimWhitespace: true});
27-
/** The owner of the repository for the pull request. */
28-
const owner = core.getInput('owner', {required: true, trimWhitespace: true});
29-
/** The pull request number. */
30-
const pr = Number(core.getInput('pr', {required: true, trimWhitespace: true}));
31-
// If the provided pr is not a number, we cannot evaluate the mergeability.
32-
if (isNaN(pr)) {
33-
core.setFailed('The provided pr value was not a number');
34-
process.exit();
35-
}
36-
/** The token for the angular robot to perform actions in the requested repo. */
37-
const token = await getAuthTokenFor(ANGULAR_ROBOT, {repo, owner});
38-
const {
39-
/** The ng-dev configuration used for the environment */
40-
config,
41-
/** The Authenticated Git Client instance. */
42-
git,
43-
} = await setupConfigAndGitClient(token, {owner, repo});
44-
/** The sha of the latest commit on the pull request, which when provided is what triggered the check. */
45-
const sha = await (async () => {
46-
let sha = core.getInput('sha', {required: false, trimWhitespace: true}) || undefined;
47-
if (sha === undefined) {
48-
sha = (await git.github.pulls.get({owner, repo, pull_number: pr})).data.head.sha as string;
31+
async function main() {
32+
/** The context name used for the commmit status applied. */
33+
const statusContextName = 'mergeability';
34+
/** The repository name for the pull request. */
35+
const repo = core.getInput('repo', {required: true, trimWhitespace: true});
36+
/** The owner of the repository for the pull request. */
37+
const owner = core.getInput('owner', {required: true, trimWhitespace: true});
38+
/** The pull request number. */
39+
const pr = Number(core.getInput('pr', {required: true, trimWhitespace: true}));
40+
/** The SHA of the pull request */
41+
let sha = core.getInput('sha', {required: false, trimWhitespace: true});
42+
43+
// If the provided pr is not a number, we cannot evaluate the mergeability.
44+
if (isNaN(pr)) {
45+
core.setFailed('The provided pr value was not a number');
46+
process.exit();
4947
}
50-
return sha;
51-
})();
52-
53-
/** Set the mergability status on the pull request provided in the environment. */
54-
async function setMergeabilityStatusOnPullRequest(
55-
{state, description, targetUrl}: CommmitStatus,
56-
canRetry = true,
57-
) {
58-
try {
59-
await git.github.repos.createCommitStatus({
60-
owner,
61-
repo,
62-
sha,
63-
context: statusContextName,
64-
state,
65-
// Status descriptions are limited to 140 characters.
66-
description: description.substring(0, 139),
67-
target_url: targetUrl,
68-
});
69-
} catch {
70-
if (canRetry) {
71-
await new Promise((resolve) => setTimeout(resolve, 5000));
72-
await setMergeabilityStatusOnPullRequest({state, description, targetUrl}, false);
48+
49+
/** Set the mergability status on the pull request provided in the environment. */
50+
async function setMergeabilityStatusOnPullRequest(
51+
git: AuthenticatedGitClient,
52+
sha: string,
53+
{state, description, targetUrl}: CommmitStatus,
54+
canRetry = true,
55+
) {
56+
try {
57+
await git.github.repos.createCommitStatus({
58+
owner,
59+
repo,
60+
sha,
61+
context: statusContextName,
62+
state,
63+
// Status descriptions are limited to 140 characters.
64+
description: description.substring(0, 139),
65+
target_url: targetUrl,
66+
});
67+
} catch {
68+
if (canRetry) {
69+
await setTimeout(5_000);
70+
await setMergeabilityStatusOnPullRequest(git, sha, {state, description, targetUrl}, false);
71+
}
7372
}
7473
}
75-
}
7674

77-
async function main() {
75+
const token = await getAuthTokenFor(ANGULAR_ROBOT, {repo, owner});
76+
7877
try {
78+
const {git} = await setupConfigAndGitClient(token, {owner, repo});
79+
sha ??= (await git.github.pulls.get({owner, repo, pull_number: pr})).data.head.sha;
80+
7981
// This is intentionally not awaited because we are just setting the status to pending, and wanting
8082
// to continue working.
81-
let _unawaitedPromise = setMergeabilityStatusOnPullRequest(
83+
void setMergeabilityStatusOnPullRequest(
84+
git,
85+
sha,
8286
{
8387
state: 'pending',
8488
description: 'Mergability check in progress',
@@ -89,6 +93,16 @@ async function main() {
8993
// Create a tmp directory to perform checks in and change working to directory to it.
9094
await cloneRepoIntoTmpLocation({owner, repo});
9195

96+
// Remove cached config and use the repo config.
97+
// TODO(alanagius): this is needed because `setupConfigAndGitClient` uses a dummy config.
98+
setConfig(null);
99+
100+
const config = await getConfig([
101+
assertValidGithubConfig,
102+
assertValidPullRequestConfig,
103+
assertValidCaretakerConfig,
104+
]);
105+
92106
/** The pull request after being retrieved and validated. */
93107
const pullRequest = await loadAndValidatePullRequest(
94108
{git, config},
@@ -168,12 +182,12 @@ async function main() {
168182
}
169183
})();
170184

171-
await setMergeabilityStatusOnPullRequest(statusInfo);
185+
await setMergeabilityStatusOnPullRequest(git, sha, statusInfo);
172186
} catch (e: Error | unknown) {
173187
let state: CommmitStatus['state'] = 'error';
174188
let description: string;
175-
const {runId, repo, serverUrl} = actionContext;
176-
const targetUrl = `${serverUrl}/${repo.owner}/${repo.repo}/actions/runs/${runId}`;
189+
const {runId, repo: actionRepo, serverUrl} = actionContext;
190+
const targetUrl = `${serverUrl}/${actionRepo.owner}/${actionRepo.repo}/actions/runs/${runId}`;
177191

178192
if (e instanceof InvalidTargetLabelError) {
179193
// For this action, an invalid target label represents that we aren't ready to check the
@@ -185,9 +199,13 @@ async function main() {
185199
} else {
186200
description = 'Internal Error, see link for action log';
187201
}
188-
await setMergeabilityStatusOnPullRequest({state, description, targetUrl});
202+
await setMergeabilityStatusOnPullRequest(git, sha, {state, description, targetUrl});
189203
// Re-throw the error so that the action run is set as failing.
190204
throw e;
205+
} finally {
206+
if (token !== undefined) {
207+
await revokeActiveInstallationToken(token);
208+
}
191209
}
192210
}
193211

@@ -196,6 +214,4 @@ try {
196214
core.error(e);
197215
core.setFailed(e.message);
198216
});
199-
} finally {
200-
await revokeActiveInstallationToken(token);
201-
}
217+
} catch {}

0 commit comments

Comments
 (0)