Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding the ability to get the NX_BASE after commits with [skip-ci] type messages #138

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ jobs:
# E.g. 'ci.yml'. If not provided, current workflow id will be used
#
workflow-id: ""

# Specifies if the NX_BASE SHA should be set to the last [skip-ci] commit that occurred after the original NX_BASE. Includes [ci skip], [no ci], [skip actions], [actions skip]. Useful if your GitHub action commits tags, version numbers, etc to the repository which should not be included
# Default: false
get-last-skip-ci-commit: false
```

<!-- end configuration-options -->
Expand Down
6 changes: 5 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ inputs:
default: "."
workflow-id:
description: "The ID of the workflow to track or name of the file name. E.g. ci.yml. Defaults to current workflow"
get-last-skip-ci-commit:
default: false
description: "Specifies if the NX_BASE SHA should be set to the last [skip-ci] commit that occurred after the original NX_BASE. Useful if your GitHub action commits tags, version numbers, etc to the repository which should not be included"

outputs:
base:
Expand All @@ -48,7 +51,8 @@ runs:
last_successful_event: ${{ inputs.last-successful-event }}
working_directory: ${{ inputs.working-directory }}
working_id: ${{ inputs.workflow-id }}
run: node "$GITHUB_ACTION_PATH/dist/index.js" "$gh_token" "$main_branch_name" "$error_on_no_successful_workflow" "$last_successful_event" "$working_directory" "$working_id"
get_last_skip_ci_commit: ${{inputs.get-last-skip-ci-commit}}
run: node "$GITHUB_ACTION_PATH/dist/index.js" "$gh_token" "$main_branch_name" "$error_on_no_successful_workflow" "$last_successful_event" "$working_directory" "$working_id" "$get_last_skip_ci_commit"

- name: Log base and head SHAs used for nx affected
shell: bash
Expand Down
92 changes: 92 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37868,8 +37868,16 @@ const lastSuccessfulEvent = process.argv[5];
const workingDirectory = process.argv[6];
const workflowId = process.argv[7];
const fallbackSHA = process.argv[8];
const getLastSkippedCommitAfterBase = process.argv[9] === "true" ? true : false;
const defaultWorkingDirectory = ".";
const ProxifiedClient = action_1.Octokit.plugin(proxyPlugin);
const messagesToSkip = [
"[skip ci]",
"[ci skip]",
"[no ci]",
"[skip actions]",
"[actions skip]",
];
let BASE_SHA;
(() => __awaiter(void 0, void 0, void 0, function* () {
if (workingDirectory !== defaultWorkingDirectory) {
Expand Down Expand Up @@ -37906,6 +37914,15 @@ let BASE_SHA;
core.setFailed(e.message);
return;
}
if (getLastSkippedCommitAfterBase && BASE_SHA) {
try {
BASE_SHA = yield findLastSkippedCommitAfterSha(stripNewLineEndings(BASE_SHA), stripNewLineEndings(HEAD_SHA), messagesToSkip, mainBranchName);
}
catch (e) {
core.setFailed(e.message);
return;
}
}
if (!BASE_SHA) {
if (errorOnNoSuccessfulWorkflow === "true") {
reportFailure(mainBranchName);
Expand All @@ -37914,6 +37931,9 @@ let BASE_SHA;
else {
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(`NOTE: You can instead make this a hard error by setting 'error-on-no-successful-workflow' on the action in your workflow.\n`);
if (fallbackSHA) {
BASE_SHA = fallbackSHA;
process.stdout.write(`Using provided fallback SHA: ${fallbackSHA}\n`);
Expand Down Expand Up @@ -38073,6 +38093,78 @@ function commitExists(octokit, branchName, commitSha) {
function stripNewLineEndings(string) {
return string.replace("\n", "");
}
/**
* Takes in an sha and then will walk forward in time finding the last commit that was a skip-ci type to use that as the new base
*/
function findLastSkippedCommitAfterSha(baseSha, headSha, messagesToSkip = [], branchName) {
return __awaiter(this, void 0, void 0, function* () {
process.stdout.write(`Checking commits from "${baseSha}" onwards for skip ci messages\n`);
if (!messagesToSkip.length) {
process.stdout.write(`messagesToSkip was empty, returning\n`);
return;
}
const octokit = new ProxifiedClient();
const baseCommit = yield getCommit(octokit, baseSha);
const headCommit = yield getCommit(octokit, headSha);
const commits = (yield findAllCommitsBetweenShas(octokit, branchName, baseCommit, headCommit)).filter((c) => c.sha !== baseSha);
const sortedCommits = commits.sort((a, b) => a.date.localeCompare(b.date));
let newBaseSha = baseSha;
for (const commit of sortedCommits) {
const containsAnySkipMessages = messagesToSkip.some((m) => commit.message.indexOf(m) >= 0);
if (containsAnySkipMessages) {
newBaseSha = commit.sha;
continue;
}
return commit.sha === headSha ? baseSha : commit.sha;
}
return newBaseSha === headSha ? baseSha : newBaseSha;
});
}
/**
* Finds all commits between two provided commits
*/
function findAllCommitsBetweenShas(octokit, branchName, baseCommit, headCommit, page = 1) {
return __awaiter(this, void 0, void 0, function* () {
let commits = (yield octokit.request("GET /repos/{owner}/{repo}/commits", {
owner,
repo,
sha: branchName,
since: baseCommit.date,
until: headCommit.date,
page,
per_page: 100,
})).data.map(getSimplifiedCommit);
const resultsContainsHead = commits.some((c) => c.sha === headCommit.sha);
if (!resultsContainsHead) {
//need to get the next page as we haven't reached the head yet.
commits = commits.concat(yield findAllCommitsBetweenShas(octokit, branchName, baseCommit, headCommit, page + 1));
}
return commits;
});
}
/**
* Gets the specified commit by its SHA
*/
function getCommit(octokit, commitSha) {
return __awaiter(this, void 0, void 0, function* () {
const fullCommit = (yield octokit.request("GET /repos/{owner}/{repo}/commits/{commit_sha}", {
owner,
repo,
commit_sha: commitSha,
})).data;
return getSimplifiedCommit(fullCommit);
});
}
/**
* strips out properties from the GitHub commit object to a simplified version for working with
*/
function getSimplifiedCommit(commit) {
return {
sha: commit.sha,
message: commit.commit.message,
date: commit.commit.committer.date,
};
}


/***/ }),
Expand Down
136 changes: 136 additions & 0 deletions find-successful-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@ const lastSuccessfulEvent = process.argv[5];
const workingDirectory = process.argv[6];
const workflowId = process.argv[7];
const fallbackSHA = process.argv[8];
const getLastSkippedCommitAfterBase = process.argv[9] === "true" ? true : false;
const defaultWorkingDirectory = ".";

const ProxifiedClient = Octokit.plugin(proxyPlugin);
const messagesToSkip = [
"[skip ci]",
"[ci skip]",
"[no ci]",
"[skip actions]",
"[actions skip]",
];

let BASE_SHA: string;
(async () => {
Expand Down Expand Up @@ -71,6 +79,19 @@ let BASE_SHA: string;
core.setFailed(e.message);
return;
}
if (getLastSkippedCommitAfterBase && BASE_SHA) {
try {
BASE_SHA = await findLastSkippedCommitAfterSha(
stripNewLineEndings(BASE_SHA),
stripNewLineEndings(HEAD_SHA),
messagesToSkip,
mainBranchName,
);
} catch (e) {
core.setFailed(e.message);
return;
}
}

if (!BASE_SHA) {
if (errorOnNoSuccessfulWorkflow === "true") {
Expand All @@ -81,6 +102,13 @@ let BASE_SHA: string;
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(
`NOTE: You can instead make this a hard error by setting 'error-on-no-successful-workflow' on the action in your workflow.\n`,
);
if (fallbackSHA) {
BASE_SHA = fallbackSHA;
process.stdout.write(`Using provided fallback SHA: ${fallbackSHA}\n`);
Expand Down Expand Up @@ -286,3 +314,111 @@ async function commitExists(
function stripNewLineEndings(string: string): string {
return string.replace("\n", "");
}
/**
* Takes in an sha and then will walk forward in time finding the last commit that was a skip-ci type to use that as the new base
*/
async function findLastSkippedCommitAfterSha(
baseSha: string,
headSha: string,
messagesToSkip: string[] = [],
branchName: string,
): Promise<string | undefined> {
process.stdout.write(
`Checking commits from "${baseSha}" onwards for skip ci messages\n`,
);
if (!messagesToSkip.length) {
process.stdout.write(`messagesToSkip was empty, returning\n`);
return;
}
const octokit = new ProxifiedClient();
const baseCommit = await getCommit(octokit, baseSha);
const headCommit = await getCommit(octokit, headSha);
const commits = (
await findAllCommitsBetweenShas(octokit, branchName, baseCommit, headCommit)
).filter((c) => c.sha !== baseSha);
const sortedCommits = commits.sort((a, b) => a.date.localeCompare(b.date));

let newBaseSha = baseSha;
for (const commit of sortedCommits) {
const containsAnySkipMessages = messagesToSkip.some(
(m) => commit.message.indexOf(m) >= 0,
);
if (containsAnySkipMessages) {
newBaseSha = commit.sha;
continue;
}
return commit.sha === headSha ? baseSha : commit.sha;
}
return newBaseSha === headSha ? baseSha : newBaseSha;
}

/**
* Finds all commits between two provided commits
*/
async function findAllCommitsBetweenShas(
octokit: Octokit,
branchName: string,
baseCommit: SimplifiedCommit,
headCommit: SimplifiedCommit,
page = 1,
): Promise<SimplifiedCommit[]> {
let commits = (
await octokit.request("GET /repos/{owner}/{repo}/commits", {
owner,
repo,
sha: branchName,
since: baseCommit.date,
until: headCommit.date,
page,
per_page: 100,
})
).data.map(getSimplifiedCommit);
const resultsContainsHead = commits.some((c) => c.sha === headCommit.sha);
if (!resultsContainsHead) {
//need to get the next page as we haven't reached the head yet.
commits = commits.concat(
await findAllCommitsBetweenShas(
octokit,
branchName,
baseCommit,
headCommit,
page + 1,
),
);
}
return commits;
}

/**
* Gets the specified commit by its SHA
*/
async function getCommit(
octokit: Octokit,
commitSha: string,
): Promise<SimplifiedCommit> {
const fullCommit = (
await octokit.request("GET /repos/{owner}/{repo}/commits/{commit_sha}", {
owner,
repo,
commit_sha: commitSha,
})
).data;
return getSimplifiedCommit(fullCommit);
}

/**
* strips out properties from the GitHub commit object to a simplified version for working with
*/
function getSimplifiedCommit(commit: any): SimplifiedCommit {
return {
sha: commit.sha,
message: commit.commit.message,
date: commit.commit.committer.date,
};
}

interface SimplifiedCommit {
sha: string;
message: string;
date: string;
}
Loading