Skip to content

Improve errors when we are trying to access a git repository with partial history (+ fix fetchGit on these repos) (backport #13265) #13266

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: 2.28-maintenance
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/libfetchers/git-utils.cc
Original file line number Diff line number Diff line change
@@ -321,8 +321,17 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>

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));
}
}
7 changes: 7 additions & 0 deletions src/libfetchers/git.cc
Original file line number Diff line number Diff line change
@@ -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 {
27 changes: 9 additions & 18 deletions tests/functional/fetchGit.sh
Original file line number Diff line number Diff line change
@@ -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
67 changes: 67 additions & 0 deletions tests/functional/fetchGitShallow.sh
Original file line number Diff line number Diff line change
@@ -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"
1 change: 1 addition & 0 deletions tests/functional/meson.build
Original file line number Diff line number Diff line change
@@ -73,6 +73,7 @@ suites = [
'gc-runtime.sh',
'tarball.sh',
'fetchGit.sh',
'fetchGitShallow.sh',
'fetchurl.sh',
'fetchPath.sh',
'fetchTree-file.sh',
Loading
Oops, something went wrong.