diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 58ba39889..7c788272f 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -321,8 +321,17 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this for (size_t n = 0; n < git_commit_parentcount(commit->get()); ++n) { git_commit * parent; - if (git_commit_parent(&parent, commit->get(), n)) - throw Error("getting parent of Git commit '%s': %s", *git_commit_id(commit->get()), git_error_last()->message); + if (git_commit_parent(&parent, commit->get(), n)) { + throw Error( + "Failed to retrieve the parent of Git commit '%s': %s. " + "This may be due to an incomplete repository history. " + "To resolve this, either enable the shallow parameter in your flake URL (?shallow=1) " + "or add set the shallow parameter to true in builtins.fetchGit, " + "or fetch the complete history for this branch.", + *git_commit_id(commit->get()), + git_error_last()->message + ); + } todo.push(Commit(parent)); } } diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 492867dc4..e72cc913f 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -798,8 +798,15 @@ struct GitInputScheme : InputScheme auto rev = repoInfo.workdirInfo.headRev.value_or(nullRev); input.attrs.insert_or_assign("rev", rev.gitRev()); +<<<<<<< HEAD input.attrs.insert_or_assign("revCount", rev == nullRev ? 0 : getRevCount(repoInfo, repoPath, rev)); +======= + if (!getShallowAttr(input)) { + input.attrs.insert_or_assign("revCount", + rev == nullRev ? 0 : getRevCount(*input.settings, repoInfo, repoPath, rev)); + } +>>>>>>> 0479db934 (fetchGit: don't compute revCount on shallow repository) verifyCommit(input, repo); } else { diff --git a/tests/functional/fetchGit.sh b/tests/functional/fetchGit.sh index 5e5e8e61f..5832facda 100755 --- a/tests/functional/fetchGit.sh +++ b/tests/functional/fetchGit.sh @@ -12,7 +12,7 @@ repo=$TEST_ROOT/./git export _NIX_FORCE_HTTP=1 -rm -rf $repo ${repo}-tmp $TEST_HOME/.cache/nix $TEST_ROOT/worktree $TEST_ROOT/shallow $TEST_ROOT/minimal +rm -rf $repo ${repo}-tmp $TEST_HOME/.cache/nix $TEST_ROOT/worktree $TEST_ROOT/minimal git init $repo git -C $repo config user.email "foobar@example.com" @@ -216,18 +216,6 @@ 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 -(! nix eval --impure --raw --expr "(builtins.fetchGit { url = $TEST_ROOT/shallow; ref = \"dev\"; }).outPath") - -# But you can request a shallow clone, which won't return a revCount. -path6=$(nix eval --impure --raw --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow\"; ref = \"dev\"; shallow = true; }).outPath") -[[ $path3 = $path6 ]] -[[ $(nix eval --impure --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow\"; ref = \"dev\"; shallow = true; }).revCount or 123") == 123 ]] - -expectStderr 1 nix eval --expr 'builtins.fetchTree { type = "git"; url = "file:///foo"; }' | grepQuiet "'fetchTree' will not fetch unlocked input" - # Explicit ref = "HEAD" should work, and produce the same outPath as without ref path7=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; }).outPath") path8=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; }).outPath") @@ -292,17 +280,20 @@ path11=$(nix eval --impure --raw --expr "(builtins.fetchGit ./.).outPath") empty="$TEST_ROOT/empty" git init "$empty" -emptyAttrs='{ lastModified = 0; lastModifiedDate = "19700101000000"; narHash = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo="; rev = "0000000000000000000000000000000000000000"; revCount = 0; shortRev = "0000000"; submodules = false; }' - -[[ $(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]") = $emptyAttrs ]] +emptyAttrs="{ lastModified = 0; lastModifiedDate = \"19700101000000\"; narHash = \"sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=\"; rev = \"0000000000000000000000000000000000000000\"; revCount = 0; shortRev = \"0000000\"; submodules = false; }" +result=$(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]") +[[ "$result" = "$emptyAttrs" ]] echo foo > "$empty/x" -[[ $(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]") = $emptyAttrs ]] +result=$(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]") +[[ "$result" = "$emptyAttrs" ]] git -C "$empty" add x -[[ $(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]") = '{ lastModified = 0; lastModifiedDate = "19700101000000"; narHash = "sha256-wzlAGjxKxpaWdqVhlq55q5Gxo4Bf860+kLeEa/v02As="; rev = "0000000000000000000000000000000000000000"; revCount = 0; shortRev = "0000000"; submodules = false; }' ]] +expected_attrs="{ lastModified = 0; lastModifiedDate = \"19700101000000\"; narHash = \"sha256-wzlAGjxKxpaWdqVhlq55q5Gxo4Bf860+kLeEa/v02As=\"; rev = \"0000000000000000000000000000000000000000\"; revCount = 0; shortRev = \"0000000\"; submodules = false; }" +result=$(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]") +[[ "$result" = "$expected_attrs" ]] # Test a repo with an empty commit. git -C "$empty" rm -f x diff --git a/tests/functional/fetchGitShallow.sh b/tests/functional/fetchGitShallow.sh new file mode 100644 index 000000000..cf7e5fd4f --- /dev/null +++ b/tests/functional/fetchGitShallow.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +# shellcheck source=common.sh +source common.sh + +requireGit + +# Create a test repo with multiple commits for all our tests +git init "$TEST_ROOT/shallow-parent" +git -C "$TEST_ROOT/shallow-parent" config user.email "foobar@example.com" +git -C "$TEST_ROOT/shallow-parent" config user.name "Foobar" + +# Add several commits to have history +echo "{ outputs = _: {}; }" > "$TEST_ROOT/shallow-parent/flake.nix" +echo "" > "$TEST_ROOT/shallow-parent/file.txt" +git -C "$TEST_ROOT/shallow-parent" add file.txt flake.nix +git -C "$TEST_ROOT/shallow-parent" commit -m "First commit" + +echo "second" > "$TEST_ROOT/shallow-parent/file.txt" +git -C "$TEST_ROOT/shallow-parent" commit -m "Second commit" -a + +echo "third" > "$TEST_ROOT/shallow-parent/file.txt" +git -C "$TEST_ROOT/shallow-parent" commit -m "Third commit" -a + +# Add a branch for testing ref fetching +git -C "$TEST_ROOT/shallow-parent" checkout -b dev +echo "branch content" > "$TEST_ROOT/shallow-parent/branch-file.txt" +git -C "$TEST_ROOT/shallow-parent" add branch-file.txt +git -C "$TEST_ROOT/shallow-parent" commit -m "Branch commit" + +# Make a shallow clone (depth=1) +git clone --depth 1 "file://$TEST_ROOT/shallow-parent" "$TEST_ROOT/shallow-clone" + +# Test 1: Fetching a shallow repo shouldn't work by default, because we can't +# return a revCount. +(! nix eval --impure --raw --expr "(builtins.fetchGit { url = \"$TEST_ROOT/shallow-clone\"; ref = \"dev\"; }).outPath") + +# Test 2: But you can request a shallow clone, which won't return a revCount. +path=$(nix eval --impure --raw --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow-clone\"; ref = \"dev\"; shallow = true; }).outPath") +# Verify file from dev branch exists +[[ -f "$path/branch-file.txt" ]] +# Verify revCount is missing +[[ $(nix eval --impure --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow-clone\"; ref = \"dev\"; shallow = true; }).revCount or 123") == 123 ]] + +# Test 3: Check unlocked input error message +expectStderr 1 nix eval --expr 'builtins.fetchTree { type = "git"; url = "file:///foo"; }' | grepQuiet "'fetchTree' will not fetch unlocked input" + +# Test 4: Regression test for revCount in worktrees derived from shallow clones +# Add a worktree to the shallow clone +git -C "$TEST_ROOT/shallow-clone" worktree add "$TEST_ROOT/shallow-worktree" + +# Prior to the fix, this would error out because of the shallow clone's +# inability to find parent commits. Now it should return an error. +if nix eval --impure --expr "(builtins.fetchGit { url = \"file://$TEST_ROOT/shallow-worktree\"; }).revCount" 2>/dev/null; then + echo "fetchGit unexpectedly succeeded on shallow clone" >&2 + exit 1 +fi + +# Also verify that fetchTree fails similarly +if nix eval --impure --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow-worktree\"; }).revCount" 2>/dev/null; then + echo "fetchTree unexpectedly succeeded on shallow clone" >&2 + exit 1 +fi + +# Verify that we can shallow fetch the worktree +git -C "$TEST_ROOT/shallow-worktree" rev-list --count HEAD >/dev/null +nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$TEST_ROOT/shallow-worktree\"; shallow = true; }).rev" diff --git a/tests/functional/meson.build b/tests/functional/meson.build index b2005d9d9..5f34f73f0 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -73,6 +73,7 @@ suites = [ 'gc-runtime.sh', 'tarball.sh', 'fetchGit.sh', + 'fetchGitShallow.sh', 'fetchurl.sh', 'fetchPath.sh', 'fetchTree-file.sh',