Skip to content

Commit 170f602

Browse files
authored
Merge pull request #13265 from Mic92/fix-shallow-clone-subset
Improve errors when we are trying to access a git repository with partial history (+ fix fetchGit on these repos)
2 parents 247f166 + 5419d82 commit 170f602

File tree

5 files changed

+92
-22
lines changed

5 files changed

+92
-22
lines changed

src/libfetchers/git-utils.cc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,17 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
322322

323323
for (size_t n = 0; n < git_commit_parentcount(commit->get()); ++n) {
324324
git_commit * parent;
325-
if (git_commit_parent(&parent, commit->get(), n))
326-
throw Error("getting parent of Git commit '%s': %s", *git_commit_id(commit->get()), git_error_last()->message);
325+
if (git_commit_parent(&parent, commit->get(), n)) {
326+
throw Error(
327+
"Failed to retrieve the parent of Git commit '%s': %s. "
328+
"This may be due to an incomplete repository history. "
329+
"To resolve this, either enable the shallow parameter in your flake URL (?shallow=1) "
330+
"or add set the shallow parameter to true in builtins.fetchGit, "
331+
"or fetch the complete history for this branch.",
332+
*git_commit_id(commit->get()),
333+
git_error_last()->message
334+
);
335+
}
327336
todo.push(Commit(parent));
328337
}
329338
}

src/libfetchers/git.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -798,8 +798,10 @@ struct GitInputScheme : InputScheme
798798
auto rev = repoInfo.workdirInfo.headRev.value_or(nullRev);
799799

800800
input.attrs.insert_or_assign("rev", rev.gitRev());
801-
input.attrs.insert_or_assign("revCount",
802-
rev == nullRev ? 0 : getRevCount(*input.settings, repoInfo, repoPath, rev));
801+
if (!getShallowAttr(input)) {
802+
input.attrs.insert_or_assign("revCount",
803+
rev == nullRev ? 0 : getRevCount(*input.settings, repoInfo, repoPath, rev));
804+
}
803805

804806
verifyCommit(input, repo);
805807
} else {

tests/functional/fetchGit.sh

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ repo=$TEST_ROOT/./git
1212

1313
export _NIX_FORCE_HTTP=1
1414

15-
rm -rf $repo ${repo}-tmp $TEST_HOME/.cache/nix $TEST_ROOT/worktree $TEST_ROOT/shallow $TEST_ROOT/minimal
15+
rm -rf $repo ${repo}-tmp $TEST_HOME/.cache/nix $TEST_ROOT/worktree $TEST_ROOT/minimal
1616

1717
git init $repo
1818
git -C $repo config user.email "[email protected]"
@@ -216,18 +216,6 @@ git -C $TEST_ROOT/minimal fetch $repo $rev2
216216
git -C $TEST_ROOT/minimal checkout $rev2
217217
[[ $(nix eval --impure --raw --expr "(builtins.fetchGit { url = $TEST_ROOT/minimal; }).rev") = $rev2 ]]
218218

219-
# Fetching a shallow repo shouldn't work by default, because we can't
220-
# return a revCount.
221-
git clone --depth 1 file://$repo $TEST_ROOT/shallow
222-
(! nix eval --impure --raw --expr "(builtins.fetchGit { url = $TEST_ROOT/shallow; ref = \"dev\"; }).outPath")
223-
224-
# But you can request a shallow clone, which won't return a revCount.
225-
path6=$(nix eval --impure --raw --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow\"; ref = \"dev\"; shallow = true; }).outPath")
226-
[[ $path3 = $path6 ]]
227-
[[ $(nix eval --impure --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow\"; ref = \"dev\"; shallow = true; }).revCount or 123") == 123 ]]
228-
229-
expectStderr 1 nix eval --expr 'builtins.fetchTree { type = "git"; url = "file:///foo"; }' | grepQuiet "'fetchTree' will not fetch unlocked input"
230-
231219
# Explicit ref = "HEAD" should work, and produce the same outPath as without ref
232220
path7=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; }).outPath")
233221
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")
292280
empty="$TEST_ROOT/empty"
293281
git init "$empty"
294282

295-
emptyAttrs='{ lastModified = 0; lastModifiedDate = "19700101000000"; narHash = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo="; rev = "0000000000000000000000000000000000000000"; revCount = 0; shortRev = "0000000"; submodules = false; }'
296-
297-
[[ $(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]") = $emptyAttrs ]]
283+
emptyAttrs="{ lastModified = 0; lastModifiedDate = \"19700101000000\"; narHash = \"sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=\"; rev = \"0000000000000000000000000000000000000000\"; revCount = 0; shortRev = \"0000000\"; submodules = false; }"
284+
result=$(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]")
285+
[[ "$result" = "$emptyAttrs" ]]
298286

299287
echo foo > "$empty/x"
300288

301-
[[ $(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]") = $emptyAttrs ]]
289+
result=$(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]")
290+
[[ "$result" = "$emptyAttrs" ]]
302291

303292
git -C "$empty" add x
304293

305-
[[ $(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; }' ]]
294+
expected_attrs="{ lastModified = 0; lastModifiedDate = \"19700101000000\"; narHash = \"sha256-wzlAGjxKxpaWdqVhlq55q5Gxo4Bf860+kLeEa/v02As=\"; rev = \"0000000000000000000000000000000000000000\"; revCount = 0; shortRev = \"0000000\"; submodules = false; }"
295+
result=$(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]")
296+
[[ "$result" = "$expected_attrs" ]]
306297

307298
# Test a repo with an empty commit.
308299
git -C "$empty" rm -f x

tests/functional/fetchGitShallow.sh

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env bash
2+
3+
# shellcheck source=common.sh
4+
source common.sh
5+
6+
requireGit
7+
8+
# Create a test repo with multiple commits for all our tests
9+
git init "$TEST_ROOT/shallow-parent"
10+
git -C "$TEST_ROOT/shallow-parent" config user.email "[email protected]"
11+
git -C "$TEST_ROOT/shallow-parent" config user.name "Foobar"
12+
13+
# Add several commits to have history
14+
echo "{ outputs = _: {}; }" > "$TEST_ROOT/shallow-parent/flake.nix"
15+
echo "" > "$TEST_ROOT/shallow-parent/file.txt"
16+
git -C "$TEST_ROOT/shallow-parent" add file.txt flake.nix
17+
git -C "$TEST_ROOT/shallow-parent" commit -m "First commit"
18+
19+
echo "second" > "$TEST_ROOT/shallow-parent/file.txt"
20+
git -C "$TEST_ROOT/shallow-parent" commit -m "Second commit" -a
21+
22+
echo "third" > "$TEST_ROOT/shallow-parent/file.txt"
23+
git -C "$TEST_ROOT/shallow-parent" commit -m "Third commit" -a
24+
25+
# Add a branch for testing ref fetching
26+
git -C "$TEST_ROOT/shallow-parent" checkout -b dev
27+
echo "branch content" > "$TEST_ROOT/shallow-parent/branch-file.txt"
28+
git -C "$TEST_ROOT/shallow-parent" add branch-file.txt
29+
git -C "$TEST_ROOT/shallow-parent" commit -m "Branch commit"
30+
31+
# Make a shallow clone (depth=1)
32+
git clone --depth 1 "file://$TEST_ROOT/shallow-parent" "$TEST_ROOT/shallow-clone"
33+
34+
# Test 1: Fetching a shallow repo shouldn't work by default, because we can't
35+
# return a revCount.
36+
(! nix eval --impure --raw --expr "(builtins.fetchGit { url = \"$TEST_ROOT/shallow-clone\"; ref = \"dev\"; }).outPath")
37+
38+
# Test 2: But you can request a shallow clone, which won't return a revCount.
39+
path=$(nix eval --impure --raw --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow-clone\"; ref = \"dev\"; shallow = true; }).outPath")
40+
# Verify file from dev branch exists
41+
[[ -f "$path/branch-file.txt" ]]
42+
# Verify revCount is missing
43+
[[ $(nix eval --impure --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow-clone\"; ref = \"dev\"; shallow = true; }).revCount or 123") == 123 ]]
44+
45+
# Test 3: Check unlocked input error message
46+
expectStderr 1 nix eval --expr 'builtins.fetchTree { type = "git"; url = "file:///foo"; }' | grepQuiet "'fetchTree' will not fetch unlocked input"
47+
48+
# Test 4: Regression test for revCount in worktrees derived from shallow clones
49+
# Add a worktree to the shallow clone
50+
git -C "$TEST_ROOT/shallow-clone" worktree add "$TEST_ROOT/shallow-worktree"
51+
52+
# Prior to the fix, this would error out because of the shallow clone's
53+
# inability to find parent commits. Now it should return an error.
54+
if nix eval --impure --expr "(builtins.fetchGit { url = \"file://$TEST_ROOT/shallow-worktree\"; }).revCount" 2>/dev/null; then
55+
echo "fetchGit unexpectedly succeeded on shallow clone" >&2
56+
exit 1
57+
fi
58+
59+
# Also verify that fetchTree fails similarly
60+
if nix eval --impure --expr "(builtins.fetchTree { type = \"git\"; url = \"file://$TEST_ROOT/shallow-worktree\"; }).revCount" 2>/dev/null; then
61+
echo "fetchTree unexpectedly succeeded on shallow clone" >&2
62+
exit 1
63+
fi
64+
65+
# Verify that we can shallow fetch the worktree
66+
git -C "$TEST_ROOT/shallow-worktree" rev-list --count HEAD >/dev/null
67+
nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$TEST_ROOT/shallow-worktree\"; shallow = true; }).rev"

tests/functional/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ suites = [
7373
'gc-runtime.sh',
7474
'tarball.sh',
7575
'fetchGit.sh',
76+
'fetchGitShallow.sh',
7677
'fetchurl.sh',
7778
'fetchPath.sh',
7879
'fetchTree-file.sh',

0 commit comments

Comments
 (0)