From 0bfa0cdea1b4eb09405e35b338887c91a041d28c Mon Sep 17 00:00:00 2001 From: Martin Schwaighofer Date: Mon, 13 Dec 2021 21:31:15 +0100 Subject: [PATCH] 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 --- src/libfetchers/git.cc | 28 ++++++++++++++-------------- tests/fetchGit.sh | 8 ++++++++ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index c3f0f8c8f..7479318e3 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -220,21 +220,21 @@ struct GitInputScheme : InputScheme if (!input.getRef() && !input.getRev() && isLocal) { bool clean = false; - /* Check whether this repo has any commits. There are - probably better ways to do this. */ - auto gitDir = actualUrl + "/.git"; - auto commonGitDir = chomp(runProgram( - "git", - true, - { "-C", actualUrl, "rev-parse", "--git-common-dir" } - )); - if (commonGitDir != ".git") - gitDir = commonGitDir; - - bool haveCommits = !readDirectory(gitDir + "/refs/heads").empty(); + /* Check whether HEAD points to something that looks like a commit, + since that is the refrence we want to use later on. */ + bool hasHead = false; + try { + runProgram("git", true, { "-C", actualUrl, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" }); + hasHead = true; + } catch (ExecError & e) { + // git exits with status 128 here if it does not detect a repository. + if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 128) { + throw Error("Git tree '%s' is broken.\n'git rev-parse --verify HEAD' failed with exit code %d.", actualUrl, WEXITSTATUS(e.status)); + } + } try { - if (haveCommits) { + if (hasHead) { runProgram("git", true, { "-C", actualUrl, "diff-index", "--quiet", "HEAD", "--" }); clean = true; } @@ -280,7 +280,7 @@ struct GitInputScheme : InputScheme // modified dirty file? input.attrs.insert_or_assign( "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}; } diff --git a/tests/fetchGit.sh b/tests/fetchGit.sh index 89294d8d2..dd0d98956 100644 --- a/tests/fetchGit.sh +++ b/tests/fetchGit.sh @@ -170,6 +170,14 @@ NIX=$(command -v nix) path5=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; ref = \"dev\"; }).outPath") [[ $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 # return a revCount. git clone --depth 1 file://$repo $TEST_ROOT/shallow