runner
runner copied to clipboard
checkout@v1: Race condition/Unable to re-run job with `fetch-depth 1`
Preliminary notes
- I'm aware that there is an /actions/checkout repository but the code for checkout@v1 is part of this repository.
- I'm aware that checkout@v2 and checkout@v3 exist but these can not be used in i386-containers; for those checkout@v1 must still be used^1^3.
Describe the bug
When fetch-depth: 1 is used in combination with checkout@v1 the git checkout command may fail.
This bug manifests itself in two ways:
- When 2 (or more) pushes are done in a short time interval and the first action run does the
git cloneafter the second push. - When re-running an older run
In both of these cases git checkout fails and the action log contains:
Warning: Git checkout failed on shallow repository, this might because of git fetch with depth '1' doesn't include the checkout commit '...'
To Reproduce
To reproduce:
- Add a workflow that uses checkout@v1 with
fetch-depth: 1 - Make a change, commit it and push it
- Make another change, commit it and push it
- Re-run the action job of the first change
A sample repository with a sample workflow: https://github.com/bram-perl/action-checkout-race-condition
Expected behavior
Checkout succeeds
Additional information
The problem happens because in checkout@v1 the git fetch command does not include the commit-sha.
Example commands:
- checkout@v1:
git ... fetch --tags --prune --progress --no-recurse-submodules --depth=1 origin +refs/heads/*:refs/remotes/origin/* - checkout@v3:
git ... fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +a3aa7af2e2582d7a73cb5f74283fbfde076162ab:refs/remotes/origin/...
checkout@v3 (and checkout@v2) explicitly include the commit-sha while checkout@v1 does not.
Possible fix
An untested fix (which is why I'm including it here and not as PR):
Show patch
diff --git a/src/Runner.Plugins/Repository/v1.0/GitSourceProvider.cs b/src/Runner.Plugins/Repository/v1.0/GitSourceProvider.cs
index a63eaaf..f9a0418 100644
--- a/src/Runner.Plugins/Repository/v1.0/GitSourceProvider.cs
+++ b/src/Runner.Plugins/Repository/v1.0/GitSourceProvider.cs
@@ -328,12 +328,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
additionalFetchSpecs.Add($"+{sourceBranch}:{GetRemoteRefName(sourceBranch)}");
}
- int exitCode_fetch = await gitCommandManager.GitFetch(executionContext, targetPath, "origin", fetchDepth, additionalFetchSpecs, string.Join(" ", additionalFetchArgs), cancellationToken);
- if (exitCode_fetch != 0)
- {
- throw new InvalidOperationException($"Git fetch failed with exit code: {exitCode_fetch}");
- }
-
// Checkout
// sourceToBuild is used for checkout
// if sourceBranch is a PR branch or sourceVersion is null, make sure branch name is a remote branch. we need checkout to detached head.
@@ -350,6 +344,14 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
sourcesToBuild = sourceVersion;
}
+ additionalFetchSpecs.Add(sourcesToBuild);
+
+ int exitCode_fetch = await gitCommandManager.GitFetch(executionContext, targetPath, "origin", fetchDepth, additionalFetchSpecs, string.Join(" ", additionalFetchArgs), cancellationToken);
+ if (exitCode_fetch != 0)
+ {
+ throw new InvalidOperationException($"Git fetch failed with exit code: {exitCode_fetch}");
+ }
+
// fetch lfs object upfront, this will avoid fetch lfs object during checkout which cause checkout taking forever
// since checkout will fetch lfs object 1 at a time, while git lfs fetch will fetch lfs object in parallel.
if (gitLfsSupport)
diff --git a/src/Runner.Plugins/Repository/v1.1/GitSourceProvider.cs b/src/Runner.Plugins/Repository/v1.1/GitSourceProvider.cs
index e138cb1..018c266 100644
--- a/src/Runner.Plugins/Repository/v1.1/GitSourceProvider.cs
+++ b/src/Runner.Plugins/Repository/v1.1/GitSourceProvider.cs
@@ -311,12 +311,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
additionalFetchSpecs.Add($"+{sourceBranch}:{GetRemoteRefName(sourceBranch)}");
}
- int exitCode_fetch = await gitCommandManager.GitFetch(executionContext, targetPath, "origin", fetchDepth, additionalFetchSpecs, string.Join(" ", additionalFetchArgs), cancellationToken);
- if (exitCode_fetch != 0)
- {
- throw new InvalidOperationException($"Git fetch failed with exit code: {exitCode_fetch}");
- }
-
// Checkout
// sourceToBuild is used for checkout
// if sourceBranch is a PR branch or sourceVersion is null, make sure branch name is a remote branch. we need checkout to detached head.
@@ -333,6 +327,14 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
sourcesToBuild = sourceVersion;
}
+ additionalFetchSpecs.Add(sourcesToBuild);
+
+ int exitCode_fetch = await gitCommandManager.GitFetch(executionContext, targetPath, "origin", fetchDepth, additionalFetchSpecs, string.Join(" ", additionalFetchArgs), cancellationToken);
+ if (exitCode_fetch != 0)
+ {
+ throw new InvalidOperationException($"Git fetch failed with exit code: {exitCode_fetch}");
+ }
+
// fetch lfs object upfront, this will avoid fetch lfs object during checkout which cause checkout taking forever
// since checkout will fetch lfs object 1 at a time, while git lfs fetch will fetch lfs object in parallel.
if (gitLfsSupport)