Skip to content

Commit 972dc3e

Browse files
author
Simon Engledew
authored
Merge pull request #428 from github/simon-engledew/detect-merge
Fix race condition with actions/checkout@v1
2 parents 5d467d0 + 9165099 commit 972dc3e

File tree

6 files changed

+83
-22
lines changed

6 files changed

+83
-22
lines changed

lib/actions-util.js

Lines changed: 17 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/actions-util.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/actions-util.test.js

Lines changed: 20 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/actions-util.test.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/actions-util.test.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,40 @@ test("getRef() returns merge PR ref if GITHUB_SHA still checked out", async (t)
2525
process.env["GITHUB_REF"] = expectedRef;
2626
process.env["GITHUB_SHA"] = currentSha;
2727

28-
sinon.stub(actionsutil, "getCommitOid").resolves(currentSha);
28+
const callback = sinon.stub(actionsutil, "getCommitOid");
29+
callback.withArgs("HEAD").resolves(currentSha);
2930

3031
const actualRef = await actionsutil.getRef();
3132
t.deepEqual(actualRef, expectedRef);
33+
callback.restore();
3234
});
3335

34-
test("getRef() returns head PR ref if GITHUB_SHA not currently checked out", async (t) => {
36+
test("getRef() returns merge PR ref if GITHUB_REF still checked out but sha has changed (actions checkout@v1)", async (t) => {
37+
const expectedRef = "refs/pull/1/merge";
38+
process.env["GITHUB_REF"] = expectedRef;
39+
process.env["GITHUB_SHA"] = "b".repeat(40);
40+
const sha = "a".repeat(40);
41+
42+
const callback = sinon.stub(actionsutil, "getCommitOid");
43+
callback.withArgs("refs/pull/1/merge").resolves(sha);
44+
callback.withArgs("HEAD").resolves(sha);
45+
46+
const actualRef = await actionsutil.getRef();
47+
t.deepEqual(actualRef, expectedRef);
48+
callback.restore();
49+
});
50+
51+
test("getRef() returns head PR ref if GITHUB_REF no longer checked out", async (t) => {
3552
process.env["GITHUB_REF"] = "refs/pull/1/merge";
3653
process.env["GITHUB_SHA"] = "a".repeat(40);
3754

38-
sinon.stub(actionsutil, "getCommitOid").resolves("b".repeat(40));
55+
const callback = sinon.stub(actionsutil, "getCommitOid");
56+
callback.withArgs("refs/pull/1/merge").resolves("a".repeat(40));
57+
callback.withArgs("HEAD").resolves("b".repeat(40));
3958

4059
const actualRef = await actionsutil.getRef();
4160
t.deepEqual(actualRef, "refs/pull/1/head");
61+
callback.restore();
4262
});
4363

4464
test("getAnalysisKey() when a local run", async (t) => {

src/actions-util.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export function prepareLocalRunEnvironment() {
7575
/**
7676
* Gets the SHA of the commit that is currently checked out.
7777
*/
78-
export const getCommitOid = async function (): Promise<string> {
78+
export const getCommitOid = async function (ref = "HEAD"): Promise<string> {
7979
// Try to use git to get the current commit SHA. If that fails then
8080
// log but otherwise silently fall back to using the SHA from the environment.
8181
// The only time these two values will differ is during analysis of a PR when
@@ -87,7 +87,7 @@ export const getCommitOid = async function (): Promise<string> {
8787
let commitOid = "";
8888
await new toolrunner.ToolRunner(
8989
await safeWhich.safeWhich("git"),
90-
["rev-parse", "HEAD"],
90+
["rev-parse", ref],
9191
{
9292
silent: true,
9393
listeners: {
@@ -425,19 +425,32 @@ export async function getRef(): Promise<string> {
425425
// Will be in the form "refs/heads/master" on a push event
426426
// or in the form "refs/pull/N/merge" on a pull_request event
427427
const ref = getRequiredEnvParam("GITHUB_REF");
428+
const sha = getRequiredEnvParam("GITHUB_SHA");
428429

429430
// For pull request refs we want to detect whether the workflow
430431
// has run `git checkout HEAD^2` to analyze the 'head' ref rather
431432
// than the 'merge' ref. If so, we want to convert the ref that
432433
// we report back.
433434
const pull_ref_regex = /refs\/pull\/(\d+)\/merge/;
434-
const checkoutSha = await getCommitOid();
435+
if (!pull_ref_regex.test(ref)) {
436+
return ref;
437+
}
438+
439+
const head = await getCommitOid("HEAD");
435440

436-
if (
437-
pull_ref_regex.test(ref) &&
438-
checkoutSha !== getRequiredEnvParam("GITHUB_SHA")
439-
) {
440-
return ref.replace(pull_ref_regex, "refs/pull/$1/head");
441+
// in actions/checkout@v2 we can check if git rev-parse HEAD == GITHUB_SHA
442+
// in actions/checkout@v1 this may not be true as it checks out the repository
443+
// using GITHUB_REF. There is a subtle race condition where
444+
// git rev-parse GITHUB_REF != GITHUB_SHA, so we must check
445+
// git git-parse GITHUB_REF == git rev-parse HEAD instead.
446+
const hasChangedRef = sha !== head && (await getCommitOid(ref)) !== head;
447+
448+
if (hasChangedRef) {
449+
const newRef = ref.replace(pull_ref_regex, "refs/pull/$1/head");
450+
core.debug(
451+
`No longer on merge commit, rewriting ref from ${ref} to ${newRef}.`
452+
);
453+
return newRef;
441454
} else {
442455
return ref;
443456
}

0 commit comments

Comments
 (0)