git fetcher: improve check for valid repository

The .git/refs/heads directory might be empty for a valid
usable git repository. This often happens in CI environments,
which might only fetch commits, not branches.
Therefore instead we let git itself check if HEAD points to
something that looks like a commit.

fixes #5302
This commit is contained in:
Martin Schwaighofer 2021-12-13 21:31:15 +01:00
parent 9bc03adbba
commit 0bfa0cdea1
2 changed files with 22 additions and 14 deletions

View file

@ -220,21 +220,21 @@ struct GitInputScheme : InputScheme
if (!input.getRef() && !input.getRev() && isLocal) { if (!input.getRef() && !input.getRev() && isLocal) {
bool clean = false; bool clean = false;
/* Check whether this repo has any commits. There are /* Check whether HEAD points to something that looks like a commit,
probably better ways to do this. */ since that is the refrence we want to use later on. */
auto gitDir = actualUrl + "/.git"; bool hasHead = false;
auto commonGitDir = chomp(runProgram( try {
"git", runProgram("git", true, { "-C", actualUrl, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" });
true, hasHead = true;
{ "-C", actualUrl, "rev-parse", "--git-common-dir" } } catch (ExecError & e) {
)); // git exits with status 128 here if it does not detect a repository.
if (commonGitDir != ".git") if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 128) {
gitDir = commonGitDir; throw Error("Git tree '%s' is broken.\n'git rev-parse --verify HEAD' failed with exit code %d.", actualUrl, WEXITSTATUS(e.status));
}
bool haveCommits = !readDirectory(gitDir + "/refs/heads").empty(); }
try { try {
if (haveCommits) { if (hasHead) {
runProgram("git", true, { "-C", actualUrl, "diff-index", "--quiet", "HEAD", "--" }); runProgram("git", true, { "-C", actualUrl, "diff-index", "--quiet", "HEAD", "--" });
clean = true; clean = true;
} }
@ -280,7 +280,7 @@ struct GitInputScheme : InputScheme
// modified dirty file? // modified dirty file?
input.attrs.insert_or_assign( input.attrs.insert_or_assign(
"lastModified", "lastModified",
haveCommits ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); hasHead ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
return {std::move(storePath), input}; return {std::move(storePath), input};
} }

View file

@ -170,6 +170,14 @@ NIX=$(command -v nix)
path5=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; ref = \"dev\"; }).outPath") path5=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; ref = \"dev\"; }).outPath")
[[ $path3 = $path5 ]] [[ $path3 = $path5 ]]
# Fetching from a repo with only a specific revision and no branches should
# not fall back to copying files and record correct revision information. See: #5302
mkdir $TEST_ROOT/minimal
git -C $TEST_ROOT/minimal init
git -C $TEST_ROOT/minimal fetch $repo $rev2
git -C $TEST_ROOT/minimal checkout $rev2
[[ $(nix eval --impure --raw --expr "(builtins.fetchGit { url = $TEST_ROOT/minimal; }).rev") = $rev2 ]]
# Fetching a shallow repo shouldn't work by default, because we can't # Fetching a shallow repo shouldn't work by default, because we can't
# return a revCount. # return a revCount.
git clone --depth 1 file://$repo $TEST_ROOT/shallow git clone --depth 1 file://$repo $TEST_ROOT/shallow