Skip to content

Commit

Permalink
fix: ensure resolved BASE_SHA was not disconnected from branch (#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesHenry authored Nov 19, 2023
1 parent fb2e040 commit 46af9ed
Show file tree
Hide file tree
Showing 6 changed files with 628 additions and 167 deletions.
8 changes: 6 additions & 2 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#!/bin/sh
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no-install lint-staged --allow-empty

node tools/pre-commit.js

node tools/pre-commit.js
36 changes: 22 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ width="100%" alt="Nx - Smart, Extensible Build Framework"></p>
**.github/workflows/ci.yml**

<!-- start example-usage -->

```yaml
# ... more CI config ...

Expand All @@ -40,7 +41,7 @@ jobs:
# ===========================================================================
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v4

- run: |
echo "BASE: ${{ env.NX_BASE }}"
echo "HEAD: ${{ env.NX_HEAD }}"
Expand All @@ -51,75 +52,81 @@ jobs:
- name: Derive appropriate SHAs for base and head for `nx affected` commands
id: setSHAs
uses: nrwl/nx-set-shas@v4

- run: |
echo "BASE: ${{ steps.setSHAs.outputs.base }}"
echo "HEAD: ${{ steps.setSHAs.outputs.head }}"
# ... more CI config ...
```

<!-- end example-usage -->

## Configuration Options

<!-- start configuration-options -->

```yaml
- uses: nrwl/nx-set-shas@v4
with:
# The "main" branch of your repository (the base branch which you target with PRs).
# Common names for this branch include main and master.
#
# Default: main
main-branch-name: ''
main-branch-name: ""

# Applies the derived SHAs for base and head as NX_BASE and NX_HEAD environment variables within the current Job.
#
# Default: true
set-environment-variables-for-job: ''
set-environment-variables-for-job: ""

# 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.
#
# Default: false
error-on-no-successful-workflow: ''
error-on-no-successful-workflow: ""

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

# 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.
#
# Default: .
working-directory: ''
working-directory: ""

# The ID of the github action workflow to check for successful run or the name of the file name containing the workflow.
# The ID of the github action workflow to check for successful run or the name of the file name containing the workflow.
# E.g. 'ci.yml'. If not provided, current workflow id will be used
#
workflow-id: ''
workflow-id: ""
```
<!-- end configuration-options -->
## Permissions in v2+
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`:

<!-- start permissions-in-v2 -->

```yaml
jobs:
myjob:
runs-on: ubuntu-latest
name: My Job
permissions:
contents: 'read'
actions: 'read'
contents: "read"
actions: "read"
```

<!-- end permissions-in-v2 -->

## Self-hosted runners

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:

<!-- self-hosted runners -->

```yaml
# ... more CI config ...
Expand Down Expand Up @@ -149,6 +156,7 @@ jobs:
# ... more CI config ...
```

<!-- end self-hosted runners -->

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

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.


### Problem

Figuring out what these two git commits are might not be as simple as it seems.
Expand All @@ -174,8 +181,9 @@ Conceptually, what we want is to use the absolute latest commit on the `master`
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.

This action enables you to find:
* Commit SHA from which PR originated (in the case of `pull_request`)
* Commit SHA of the last successful CI run

- Commit SHA from which PR originated (in the case of `pull_request`)
- Commit SHA of the last successful CI run

## License

Expand Down
113 changes: 63 additions & 50 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37853,21 +37853,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const action_1 = __nccwpck_require__(1231);
const core = __nccwpck_require__(2186);
const github = __nccwpck_require__(5438);
const action_1 = __nccwpck_require__(1231);
const child_process_1 = __nccwpck_require__(2081);
const fs_1 = __nccwpck_require__(7147);
const https_proxy_agent_1 = __nccwpck_require__(7219);
const proxy_from_env_1 = __nccwpck_require__(3329);
const { runId, repo: { repo, owner }, eventName } = github.context;
const { runId, repo: { repo, owner }, eventName, } = github.context;
process.env.GITHUB_TOKEN = process.argv[2];
const mainBranchName = process.argv[3];
const errorOnNoSuccessfulWorkflow = process.argv[4];
const lastSuccessfulEvent = process.argv[5];
const workingDirectory = process.argv[6];
const workflowId = process.argv[7];
const defaultWorkingDirectory = '.';
const defaultWorkingDirectory = ".";
const ProxifiedClient = action_1.Octokit.plugin(proxyPlugin);
let BASE_SHA;
(() => __awaiter(void 0, void 0, void 0, function* () {
Expand All @@ -37876,17 +37876,20 @@ let BASE_SHA;
process.chdir(workingDirectory);
}
else {
process.stdout.write('\n');
process.stdout.write("\n");
process.stdout.write(`WARNING: Working directory '${workingDirectory}' doesn't exist.\n`);
}
}
const headResult = (0, child_process_1.spawnSync)('git', ['rev-parse', 'HEAD'], { encoding: 'utf-8' });
const headResult = (0, child_process_1.spawnSync)("git", ["rev-parse", "HEAD"], {
encoding: "utf-8",
});
const HEAD_SHA = headResult.stdout;
if ((['pull_request', 'pull_request_target'].includes(eventName) && !github.context.payload.pull_request.merged) ||
eventName == 'merge_group') {
if ((["pull_request", "pull_request_target"].includes(eventName) &&
!github.context.payload.pull_request.merged) ||
eventName == "merge_group") {
try {
const mergeBaseRef = yield findMergeBaseRef();
const baseResult = (0, child_process_1.spawnSync)('git', ['merge-base', `origin/${mainBranchName}`, mergeBaseRef], { encoding: 'utf-8' });
const baseResult = (0, child_process_1.spawnSync)("git", ["merge-base", `origin/${mainBranchName}`, mergeBaseRef], { encoding: "utf-8" });
BASE_SHA = baseResult.stdout;
}
catch (e) {
Expand All @@ -37903,32 +37906,35 @@ let BASE_SHA;
return;
}
if (!BASE_SHA) {
if (errorOnNoSuccessfulWorkflow === 'true') {
if (errorOnNoSuccessfulWorkflow === "true") {
reportFailure(mainBranchName);
return;
}
else {
process.stdout.write('\n');
process.stdout.write(`WARNING: Unable to find a successful workflow run on 'origin/${mainBranchName}'\n`);
process.stdout.write("\n");
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`);
process.stdout.write(`We are therefore defaulting to use HEAD~1 on 'origin/${mainBranchName}'\n`);
process.stdout.write('\n');
process.stdout.write("\n");
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`);
const commitCountOutput = (0, child_process_1.spawnSync)('git', ['rev-list', '--count', `origin/${mainBranchName}`], { encoding: 'utf-8' }).stdout;
process.stdout.write("\n");
const commitCountOutput = (0, child_process_1.spawnSync)("git", ["rev-list", "--count", `origin/${mainBranchName}`], { encoding: "utf-8" }).stdout;
const commitCount = parseInt(stripNewLineEndings(commitCountOutput), 10);
const LAST_COMMIT_CMD = `origin/${mainBranchName}${commitCount > 1 ? '~1' : ''}`;
const baseRes = (0, child_process_1.spawnSync)('git', ['rev-parse', LAST_COMMIT_CMD], { encoding: 'utf-8' });
const LAST_COMMIT_CMD = `origin/${mainBranchName}${commitCount > 1 ? "~1" : ""}`;
const baseRes = (0, child_process_1.spawnSync)("git", ["rev-parse", LAST_COMMIT_CMD], {
encoding: "utf-8",
});
BASE_SHA = baseRes.stdout;
core.setOutput('noPreviousBuild', 'true');
core.setOutput("noPreviousBuild", "true");
}
}
else {
process.stdout.write('\n');
process.stdout.write("\n");
process.stdout.write(`Found the last successful workflow run on 'origin/${mainBranchName}'\n`);
process.stdout.write(`Commit: ${BASE_SHA}\n`);
}
}
core.setOutput('base', stripNewLineEndings(BASE_SHA));
core.setOutput('head', stripNewLineEndings(HEAD_SHA));
core.setOutput("base", stripNewLineEndings(BASE_SHA));
core.setOutput("head", stripNewLineEndings(HEAD_SHA));
}))();
function reportFailure(branchName) {
core.setFailed(`
Expand All @@ -37940,7 +37946,7 @@ function reportFailure(branchName) {
- If no, then you might have changed your git history and those commits no longer exist.`);
}
function proxyPlugin(octokit) {
octokit.hook.before('request', options => {
octokit.hook.before("request", (options) => {
const proxy = (0, proxy_from_env_1.getProxyForUrl)(options.baseUrl);
if (proxy) {
options.request.agent = new https_proxy_agent_1.HttpsProxyAgent(proxy);
Expand All @@ -37949,47 +37955,45 @@ function proxyPlugin(octokit) {
}
/**
* Find last successful workflow run on the repo
* @param {string?} workflow_id
* @param {number} run_id
* @param {string} owner
* @param {string} repo
* @param {string} branch
* @returns
*/
function findSuccessfulCommit(workflow_id, run_id, owner, repo, branch, lastSuccessfulEvent) {
return __awaiter(this, void 0, void 0, function* () {
const octokit = new ProxifiedClient();
if (!workflow_id) {
workflow_id = yield octokit.request(`GET /repos/${owner}/${repo}/actions/runs/${run_id}`, {
workflow_id = yield octokit
.request(`GET /repos/${owner}/${repo}/actions/runs/${run_id}`, {
owner,
repo,
branch,
run_id
}).then(({ data: { workflow_id } }) => workflow_id);
process.stdout.write('\n');
run_id,
})
.then(({ data: { workflow_id } }) => workflow_id);
process.stdout.write("\n");
process.stdout.write(`Workflow Id not provided. Using workflow '${workflow_id}'\n`);
}
// fetch all workflow runs on a given repo/branch/workflow with push and success
const shas = yield octokit.request(`GET /repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs`, {
const shas = yield octokit
.request(`GET /repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs`, {
owner,
repo,
// on non-push workflow runs we do not have branch property
branch: lastSuccessfulEvent !== 'push' ? undefined : branch,
branch: lastSuccessfulEvent !== "push" ? undefined : branch,
workflow_id,
event: lastSuccessfulEvent,
status: 'success'
}).then(({ data: { workflow_runs } }) => workflow_runs.map(run => run.head_sha));
return yield findExistingCommit(shas);
status: "success",
})
.then(({ data: { workflow_runs } }) => workflow_runs.map((run) => run.head_sha));
return yield findExistingCommit(octokit, branch, shas);
});
}
function findMergeBaseRef() {
return __awaiter(this, void 0, void 0, function* () {
if (eventName == 'merge_group') {
if (eventName == "merge_group") {
const mergeQueueBranch = yield findMergeQueueBranch();
return `origin/${mergeQueueBranch}`;
}
else {
return 'HEAD';
return "HEAD";
}
});
}
Expand All @@ -38002,9 +38006,9 @@ function findMergeQueueBranch() {
return __awaiter(this, void 0, void 0, function* () {
const pull_number = findMergeQueuePr();
if (!pull_number) {
throw new Error('Failed to determine PR number');
throw new Error("Failed to determine PR number");
}
process.stdout.write('\n');
process.stdout.write("\n");
process.stdout.write(`Found PR #${pull_number} from merge queue branch\n`);
const octokit = new ProxifiedClient();
const result = yield octokit.request(`GET /repos/${owner}/${repo}/pulls/${pull_number}`, { owner, repo, pull_number: +pull_number });
Expand All @@ -38013,13 +38017,11 @@ function findMergeQueueBranch() {
}
/**
* Get first existing commit
* @param {string[]} commit_shas
* @returns {string?}
*/
function findExistingCommit(shas) {
function findExistingCommit(octokit, branchName, shas) {
return __awaiter(this, void 0, void 0, function* () {
for (const commitSha of shas) {
if (yield commitExists(commitSha)) {
if (yield commitExists(octokit, branchName, commitSha)) {
return commitSha;
}
}
Expand All @@ -38028,14 +38030,26 @@ function findExistingCommit(shas) {
}
/**
* Check if given commit is valid
* @param {string} commitSha
* @returns {boolean}
*/
function commitExists(commitSha) {
function commitExists(octokit, branchName, commitSha) {
return __awaiter(this, void 0, void 0, function* () {
try {
(0, child_process_1.spawnSync)('git', ['cat-file', '-e', commitSha], { stdio: ['pipe', 'pipe', null] });
return true;
(0, child_process_1.spawnSync)("git", ["cat-file", "-e", commitSha], {
stdio: ["pipe", "pipe", null],
});
// Check the commit exists in general
yield octokit.request("GET /repos/{owner}/{repo}/commits/{commit_sha}", {
owner,
repo,
commit_sha: commitSha,
});
// Check the commit exists on the expected main branch (it will not in the case of a rebased main branch)
const commits = yield octokit.request("GET /repos/{owner}/{repo}/commits", {
owner,
repo,
sha: branchName,
});
return commits.data.some((commit) => commit.sha === commitSha);
}
catch (_a) {
return false;
Expand All @@ -38044,10 +38058,9 @@ function commitExists(commitSha) {
}
/**
* Strips LF line endings from given string
* @param {string} string
*/
function stripNewLineEndings(string) {
return string.replace('\n', '');
return string.replace("\n", "");
}


Expand Down
Loading

0 comments on commit 46af9ed

Please sign in to comment.