Skip to content

Commit 46af9ed

Browse files
authored
fix: ensure resolved BASE_SHA was not disconnected from branch (#132)
1 parent fb2e040 commit 46af9ed

6 files changed

Lines changed: 628 additions & 167 deletions

File tree

.husky/pre-commit

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
#!/bin/sh
1+
#!/usr/bin/env sh
2+
. "$(dirname -- "$0")/_/husky.sh"
3+
4+
npx --no-install lint-staged --allow-empty
5+
6+
node tools/pre-commit.js
27

3-
node tools/pre-commit.js

README.md

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ width="100%" alt="Nx - Smart, Extensible Build Framework"></p>
1919
**.github/workflows/ci.yml**
2020

2121
<!-- start example-usage -->
22+
2223
```yaml
2324
# ... more CI config ...
2425

@@ -40,7 +41,7 @@ jobs:
4041
# ===========================================================================
4142
- name: Derive appropriate SHAs for base and head for `nx affected` commands
4243
uses: nrwl/nx-set-shas@v4
43-
44+
4445
- run: |
4546
echo "BASE: ${{ env.NX_BASE }}"
4647
echo "HEAD: ${{ env.NX_HEAD }}"
@@ -51,75 +52,81 @@ jobs:
5152
- name: Derive appropriate SHAs for base and head for `nx affected` commands
5253
id: setSHAs
5354
uses: nrwl/nx-set-shas@v4
54-
55+
5556
- run: |
5657
echo "BASE: ${{ steps.setSHAs.outputs.base }}"
5758
echo "HEAD: ${{ steps.setSHAs.outputs.head }}"
5859
5960
# ... more CI config ...
6061
```
62+
6163
<!-- end example-usage -->
6264

6365
## Configuration Options
6466

6567
<!-- start configuration-options -->
68+
6669
```yaml
6770
- uses: nrwl/nx-set-shas@v4
6871
with:
6972
# The "main" branch of your repository (the base branch which you target with PRs).
7073
# Common names for this branch include main and master.
7174
#
7275
# Default: main
73-
main-branch-name: ''
76+
main-branch-name: ""
7477

7578
# Applies the derived SHAs for base and head as NX_BASE and NX_HEAD environment variables within the current Job.
7679
#
7780
# Default: true
78-
set-environment-variables-for-job: ''
81+
set-environment-variables-for-job: ""
7982

8083
# By default, if no successful workflow run is found on the main branch to determine the SHA, we will log a warning and use HEAD~1. Enable this option to error and exit instead.
8184
#
8285
# Default: false
83-
error-on-no-successful-workflow: ''
86+
error-on-no-successful-workflow: ""
8487

8588
# The type of event to check for the last successful commit corresponding to that workflow-id, e.g. push, pull_request, release etc.
8689
#
8790
# Default: push
88-
last-successful-event: ''
91+
last-successful-event: ""
8992

9093
# The path where your repository is. This is only required for cases where the repository code is checked out or moved to a specific path.
9194
#
9295
# Default: .
93-
working-directory: ''
96+
working-directory: ""
9497

95-
# The ID of the github action workflow to check for successful run or the name of the file name containing the workflow.
98+
# The ID of the github action workflow to check for successful run or the name of the file name containing the workflow.
9699
# E.g. 'ci.yml'. If not provided, current workflow id will be used
97100
#
98-
workflow-id: ''
101+
workflow-id: ""
99102
```
103+
100104
<!-- end configuration-options -->
101105
102106
## Permissions in v2+
103107
104108
This Action uses Github API to find the last successful workflow run. If your `GITHUB_TOKEN` has restrictions set please ensure you override them for the workflow to enable read access to `actions` and `contents`:
105109

106110
<!-- start permissions-in-v2 -->
111+
107112
```yaml
108113
jobs:
109114
myjob:
110115
runs-on: ubuntu-latest
111116
name: My Job
112117
permissions:
113-
contents: 'read'
114-
actions: 'read'
118+
contents: "read"
119+
actions: "read"
115120
```
121+
116122
<!-- end permissions-in-v2 -->
117123

118124
## Self-hosted runners
119125

120126
This Action supports usage of your own self-hosted runners, but since it uses GitHub APIs you will need to grant it explicit access rights:
121127

122128
<!-- self-hosted runners -->
129+
123130
```yaml
124131
# ... more CI config ...
125132
@@ -149,6 +156,7 @@ jobs:
149156
150157
# ... more CI config ...
151158
```
159+
152160
<!-- end self-hosted runners -->
153161

154162
## Background
@@ -157,7 +165,6 @@ When we run the `affected` command on [Nx](https://nx.dev/), we can specify 2 gi
157165

158166
This makes it easy to set up a CI system that scales well with the continuous growth of your repository, as you add more and more projects.
159167

160-
161168
### Problem
162169

163170
Figuring out what these two git commits are might not be as simple as it seems.
@@ -174,8 +181,9 @@ Conceptually, what we want is to use the absolute latest commit on the `master`
174181
The commits therefore can't just be `HEAD` and `HEAD~1`. If a few deployments fail one after another, that means that we're accumulating a list of affected projects that are not getting deployed. Anytime we retry the deployment, we want to include **every commit since the last time we deployed successfully**. That way we ensure we don't accidentally skip deploying a project that has changed.
175182

176183
This action enables you to find:
177-
* Commit SHA from which PR originated (in the case of `pull_request`)
178-
* Commit SHA of the last successful CI run
184+
185+
- Commit SHA from which PR originated (in the case of `pull_request`)
186+
- Commit SHA of the last successful CI run
179187

180188
## License
181189

dist/index.js

Lines changed: 63 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -37853,21 +37853,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
3785337853
});
3785437854
};
3785537855
Object.defineProperty(exports, "__esModule", ({ value: true }));
37856-
const action_1 = __nccwpck_require__(1231);
3785737856
const core = __nccwpck_require__(2186);
3785837857
const github = __nccwpck_require__(5438);
37858+
const action_1 = __nccwpck_require__(1231);
3785937859
const child_process_1 = __nccwpck_require__(2081);
3786037860
const fs_1 = __nccwpck_require__(7147);
3786137861
const https_proxy_agent_1 = __nccwpck_require__(7219);
3786237862
const proxy_from_env_1 = __nccwpck_require__(3329);
37863-
const { runId, repo: { repo, owner }, eventName } = github.context;
37863+
const { runId, repo: { repo, owner }, eventName, } = github.context;
3786437864
process.env.GITHUB_TOKEN = process.argv[2];
3786537865
const mainBranchName = process.argv[3];
3786637866
const errorOnNoSuccessfulWorkflow = process.argv[4];
3786737867
const lastSuccessfulEvent = process.argv[5];
3786837868
const workingDirectory = process.argv[6];
3786937869
const workflowId = process.argv[7];
37870-
const defaultWorkingDirectory = '.';
37870+
const defaultWorkingDirectory = ".";
3787137871
const ProxifiedClient = action_1.Octokit.plugin(proxyPlugin);
3787237872
let BASE_SHA;
3787337873
(() => __awaiter(void 0, void 0, void 0, function* () {
@@ -37876,17 +37876,20 @@ let BASE_SHA;
3787637876
process.chdir(workingDirectory);
3787737877
}
3787837878
else {
37879-
process.stdout.write('\n');
37879+
process.stdout.write("\n");
3788037880
process.stdout.write(`WARNING: Working directory '${workingDirectory}' doesn't exist.\n`);
3788137881
}
3788237882
}
37883-
const headResult = (0, child_process_1.spawnSync)('git', ['rev-parse', 'HEAD'], { encoding: 'utf-8' });
37883+
const headResult = (0, child_process_1.spawnSync)("git", ["rev-parse", "HEAD"], {
37884+
encoding: "utf-8",
37885+
});
3788437886
const HEAD_SHA = headResult.stdout;
37885-
if ((['pull_request', 'pull_request_target'].includes(eventName) && !github.context.payload.pull_request.merged) ||
37886-
eventName == 'merge_group') {
37887+
if ((["pull_request", "pull_request_target"].includes(eventName) &&
37888+
!github.context.payload.pull_request.merged) ||
37889+
eventName == "merge_group") {
3788737890
try {
3788837891
const mergeBaseRef = yield findMergeBaseRef();
37889-
const baseResult = (0, child_process_1.spawnSync)('git', ['merge-base', `origin/${mainBranchName}`, mergeBaseRef], { encoding: 'utf-8' });
37892+
const baseResult = (0, child_process_1.spawnSync)("git", ["merge-base", `origin/${mainBranchName}`, mergeBaseRef], { encoding: "utf-8" });
3789037893
BASE_SHA = baseResult.stdout;
3789137894
}
3789237895
catch (e) {
@@ -37903,32 +37906,35 @@ let BASE_SHA;
3790337906
return;
3790437907
}
3790537908
if (!BASE_SHA) {
37906-
if (errorOnNoSuccessfulWorkflow === 'true') {
37909+
if (errorOnNoSuccessfulWorkflow === "true") {
3790737910
reportFailure(mainBranchName);
3790837911
return;
3790937912
}
3791037913
else {
37911-
process.stdout.write('\n');
37912-
process.stdout.write(`WARNING: Unable to find a successful workflow run on 'origin/${mainBranchName}'\n`);
37914+
process.stdout.write("\n");
37915+
process.stdout.write(`WARNING: Unable to find a successful workflow run on 'origin/${mainBranchName}', or the latest successful workflow was connected to a commit which no longer exists on that branch (e.g. if that branch was rebased)\n`);
3791337916
process.stdout.write(`We are therefore defaulting to use HEAD~1 on 'origin/${mainBranchName}'\n`);
37914-
process.stdout.write('\n');
37917+
process.stdout.write("\n");
3791537918
process.stdout.write(`NOTE: You can instead make this a hard error by setting 'error-on-no-successful-workflow' on the action in your workflow.\n`);
37916-
const commitCountOutput = (0, child_process_1.spawnSync)('git', ['rev-list', '--count', `origin/${mainBranchName}`], { encoding: 'utf-8' }).stdout;
37919+
process.stdout.write("\n");
37920+
const commitCountOutput = (0, child_process_1.spawnSync)("git", ["rev-list", "--count", `origin/${mainBranchName}`], { encoding: "utf-8" }).stdout;
3791737921
const commitCount = parseInt(stripNewLineEndings(commitCountOutput), 10);
37918-
const LAST_COMMIT_CMD = `origin/${mainBranchName}${commitCount > 1 ? '~1' : ''}`;
37919-
const baseRes = (0, child_process_1.spawnSync)('git', ['rev-parse', LAST_COMMIT_CMD], { encoding: 'utf-8' });
37922+
const LAST_COMMIT_CMD = `origin/${mainBranchName}${commitCount > 1 ? "~1" : ""}`;
37923+
const baseRes = (0, child_process_1.spawnSync)("git", ["rev-parse", LAST_COMMIT_CMD], {
37924+
encoding: "utf-8",
37925+
});
3792037926
BASE_SHA = baseRes.stdout;
37921-
core.setOutput('noPreviousBuild', 'true');
37927+
core.setOutput("noPreviousBuild", "true");
3792237928
}
3792337929
}
3792437930
else {
37925-
process.stdout.write('\n');
37931+
process.stdout.write("\n");
3792637932
process.stdout.write(`Found the last successful workflow run on 'origin/${mainBranchName}'\n`);
3792737933
process.stdout.write(`Commit: ${BASE_SHA}\n`);
3792837934
}
3792937935
}
37930-
core.setOutput('base', stripNewLineEndings(BASE_SHA));
37931-
core.setOutput('head', stripNewLineEndings(HEAD_SHA));
37936+
core.setOutput("base", stripNewLineEndings(BASE_SHA));
37937+
core.setOutput("head", stripNewLineEndings(HEAD_SHA));
3793237938
}))();
3793337939
function reportFailure(branchName) {
3793437940
core.setFailed(`
@@ -37940,7 +37946,7 @@ function reportFailure(branchName) {
3794037946
- If no, then you might have changed your git history and those commits no longer exist.`);
3794137947
}
3794237948
function proxyPlugin(octokit) {
37943-
octokit.hook.before('request', options => {
37949+
octokit.hook.before("request", (options) => {
3794437950
const proxy = (0, proxy_from_env_1.getProxyForUrl)(options.baseUrl);
3794537951
if (proxy) {
3794637952
options.request.agent = new https_proxy_agent_1.HttpsProxyAgent(proxy);
@@ -37949,47 +37955,45 @@ function proxyPlugin(octokit) {
3794937955
}
3795037956
/**
3795137957
* Find last successful workflow run on the repo
37952-
* @param {string?} workflow_id
37953-
* @param {number} run_id
37954-
* @param {string} owner
37955-
* @param {string} repo
37956-
* @param {string} branch
37957-
* @returns
3795837958
*/
3795937959
function findSuccessfulCommit(workflow_id, run_id, owner, repo, branch, lastSuccessfulEvent) {
3796037960
return __awaiter(this, void 0, void 0, function* () {
3796137961
const octokit = new ProxifiedClient();
3796237962
if (!workflow_id) {
37963-
workflow_id = yield octokit.request(`GET /repos/${owner}/${repo}/actions/runs/${run_id}`, {
37963+
workflow_id = yield octokit
37964+
.request(`GET /repos/${owner}/${repo}/actions/runs/${run_id}`, {
3796437965
owner,
3796537966
repo,
3796637967
branch,
37967-
run_id
37968-
}).then(({ data: { workflow_id } }) => workflow_id);
37969-
process.stdout.write('\n');
37968+
run_id,
37969+
})
37970+
.then(({ data: { workflow_id } }) => workflow_id);
37971+
process.stdout.write("\n");
3797037972
process.stdout.write(`Workflow Id not provided. Using workflow '${workflow_id}'\n`);
3797137973
}
3797237974
// fetch all workflow runs on a given repo/branch/workflow with push and success
37973-
const shas = yield octokit.request(`GET /repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs`, {
37975+
const shas = yield octokit
37976+
.request(`GET /repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs`, {
3797437977
owner,
3797537978
repo,
3797637979
// on non-push workflow runs we do not have branch property
37977-
branch: lastSuccessfulEvent !== 'push' ? undefined : branch,
37980+
branch: lastSuccessfulEvent !== "push" ? undefined : branch,
3797837981
workflow_id,
3797937982
event: lastSuccessfulEvent,
37980-
status: 'success'
37981-
}).then(({ data: { workflow_runs } }) => workflow_runs.map(run => run.head_sha));
37982-
return yield findExistingCommit(shas);
37983+
status: "success",
37984+
})
37985+
.then(({ data: { workflow_runs } }) => workflow_runs.map((run) => run.head_sha));
37986+
return yield findExistingCommit(octokit, branch, shas);
3798337987
});
3798437988
}
3798537989
function findMergeBaseRef() {
3798637990
return __awaiter(this, void 0, void 0, function* () {
37987-
if (eventName == 'merge_group') {
37991+
if (eventName == "merge_group") {
3798837992
const mergeQueueBranch = yield findMergeQueueBranch();
3798937993
return `origin/${mergeQueueBranch}`;
3799037994
}
3799137995
else {
37992-
return 'HEAD';
37996+
return "HEAD";
3799337997
}
3799437998
});
3799537999
}
@@ -38002,9 +38006,9 @@ function findMergeQueueBranch() {
3800238006
return __awaiter(this, void 0, void 0, function* () {
3800338007
const pull_number = findMergeQueuePr();
3800438008
if (!pull_number) {
38005-
throw new Error('Failed to determine PR number');
38009+
throw new Error("Failed to determine PR number");
3800638010
}
38007-
process.stdout.write('\n');
38011+
process.stdout.write("\n");
3800838012
process.stdout.write(`Found PR #${pull_number} from merge queue branch\n`);
3800938013
const octokit = new ProxifiedClient();
3801038014
const result = yield octokit.request(`GET /repos/${owner}/${repo}/pulls/${pull_number}`, { owner, repo, pull_number: +pull_number });
@@ -38013,13 +38017,11 @@ function findMergeQueueBranch() {
3801338017
}
3801438018
/**
3801538019
* Get first existing commit
38016-
* @param {string[]} commit_shas
38017-
* @returns {string?}
3801838020
*/
38019-
function findExistingCommit(shas) {
38021+
function findExistingCommit(octokit, branchName, shas) {
3802038022
return __awaiter(this, void 0, void 0, function* () {
3802138023
for (const commitSha of shas) {
38022-
if (yield commitExists(commitSha)) {
38024+
if (yield commitExists(octokit, branchName, commitSha)) {
3802338025
return commitSha;
3802438026
}
3802538027
}
@@ -38028,14 +38030,26 @@ function findExistingCommit(shas) {
3802838030
}
3802938031
/**
3803038032
* Check if given commit is valid
38031-
* @param {string} commitSha
38032-
* @returns {boolean}
3803338033
*/
38034-
function commitExists(commitSha) {
38034+
function commitExists(octokit, branchName, commitSha) {
3803538035
return __awaiter(this, void 0, void 0, function* () {
3803638036
try {
38037-
(0, child_process_1.spawnSync)('git', ['cat-file', '-e', commitSha], { stdio: ['pipe', 'pipe', null] });
38038-
return true;
38037+
(0, child_process_1.spawnSync)("git", ["cat-file", "-e", commitSha], {
38038+
stdio: ["pipe", "pipe", null],
38039+
});
38040+
// Check the commit exists in general
38041+
yield octokit.request("GET /repos/{owner}/{repo}/commits/{commit_sha}", {
38042+
owner,
38043+
repo,
38044+
commit_sha: commitSha,
38045+
});
38046+
// Check the commit exists on the expected main branch (it will not in the case of a rebased main branch)
38047+
const commits = yield octokit.request("GET /repos/{owner}/{repo}/commits", {
38048+
owner,
38049+
repo,
38050+
sha: branchName,
38051+
});
38052+
return commits.data.some((commit) => commit.sha === commitSha);
3803938053
}
3804038054
catch (_a) {
3804138055
return false;
@@ -38044,10 +38058,9 @@ function commitExists(commitSha) {
3804438058
}
3804538059
/**
3804638060
* Strips LF line endings from given string
38047-
* @param {string} string
3804838061
*/
3804938062
function stripNewLineEndings(string) {
38050-
return string.replace('\n', '');
38063+
return string.replace("\n", "");
3805138064
}
3805238065

3805338066

0 commit comments

Comments
 (0)