Skip to content

Commit f14ea84

Browse files
authored
Merge pull request #270 from DeterminateSystems/shallow-optimization
Avoid unnecessary Git refetches
2 parents 6bf5230 + ced5dbe commit f14ea84

File tree

6 files changed

+80
-21
lines changed

6 files changed

+80
-21
lines changed

src/libexpr/primops/fetchTree.cc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,6 @@ static void fetchTree(
150150
attrs.emplace("exportIgnore", Explicit<bool>{true});
151151
}
152152

153-
// fetchTree should fetch git repos with shallow = true by default
154-
if (type == "git" && !params.isFetchGit && !attrs.contains("shallow")) {
155-
attrs.emplace("shallow", Explicit<bool>{true});
156-
}
157-
158153
if (!params.allowNameArgument)
159154
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
160155
state.error<EvalError>("argument 'name' isn’t supported in call to '%s'", fetcher)

src/libfetchers/git.cc

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,16 @@ struct GitInputScheme : InputScheme
627627
}
628628
}
629629

630+
/**
631+
* Decide whether we can do a shallow clone, which is faster. This is possible if the user explicitly specified
632+
* `shallow = true`, or if we already have a `revCount`.
633+
*/
634+
bool canDoShallow(const Input & input) const
635+
{
636+
bool shallow = getShallowAttr(input);
637+
return shallow || input.getRevCount().has_value();
638+
}
639+
630640
std::pair<ref<SourceAccessor>, Input>
631641
getAccessorFromCommit(const Settings & settings, ref<Store> store, RepoInfo & repoInfo, Input && input) const
632642
{
@@ -635,7 +645,7 @@ struct GitInputScheme : InputScheme
635645
auto origRev = input.getRev();
636646

637647
auto originalRef = input.getRef();
638-
bool shallow = getShallowAttr(input);
648+
bool shallow = canDoShallow(input);
639649
auto ref = originalRef ? *originalRef : getDefaultRef(repoInfo, shallow);
640650
input.attrs.insert_or_assign("ref", ref);
641651

@@ -646,11 +656,27 @@ struct GitInputScheme : InputScheme
646656
if (!input.getRev())
647657
input.attrs.insert_or_assign("rev", GitRepo::openRepo(repoDir)->resolveRef(ref).gitRev());
648658
} else {
659+
auto rev = input.getRev();
649660
auto repoUrl = std::get<ParsedURL>(repoInfo.location);
650661
std::filesystem::path cacheDir = getCachePath(repoUrl.to_string(), shallow);
651662
repoDir = cacheDir;
652663
repoInfo.gitDir = ".";
653664

665+
/* If shallow = false, but we have a non-shallow repo that already contains the desired rev, then use that
666+
* repo instead. */
667+
std::filesystem::path cacheDirNonShallow = getCachePath(repoUrl.to_string(), false);
668+
if (rev && shallow && pathExists(cacheDirNonShallow)) {
669+
auto nonShallowRepo = GitRepo::openRepo(cacheDirNonShallow, true, true);
670+
if (nonShallowRepo->hasObject(*rev)) {
671+
debug(
672+
"using non-shallow cached repo for '%s' since it contains rev '%s'",
673+
repoUrl.to_string(),
674+
rev->gitRev());
675+
repoDir = cacheDirNonShallow;
676+
goto have_rev;
677+
}
678+
}
679+
654680
std::filesystem::create_directories(cacheDir.parent_path());
655681
PathLocks cacheDirLock({cacheDir.string()});
656682

@@ -666,7 +692,7 @@ struct GitInputScheme : InputScheme
666692

667693
/* If a rev was specified, we need to fetch if it's not in the
668694
repo. */
669-
if (auto rev = input.getRev()) {
695+
if (rev) {
670696
doFetch = !repo->hasObject(*rev);
671697
} else {
672698
if (getAllRefsAttr(input)) {
@@ -680,7 +706,6 @@ struct GitInputScheme : InputScheme
680706
}
681707

682708
if (doFetch) {
683-
bool shallow = getShallowAttr(input);
684709
try {
685710
auto fetchRef = getAllRefsAttr(input) ? "refs/*:refs/*"
686711
: input.getRev() ? input.getRev()->gitRev()
@@ -708,7 +733,7 @@ struct GitInputScheme : InputScheme
708733
warn("could not update cached head '%s' for '%s'", ref, repoInfo.locationToArg());
709734
}
710735

711-
if (auto rev = input.getRev()) {
736+
if (rev) {
712737
if (!repo->hasObject(*rev))
713738
throw Error(
714739
"Cannot find Git revision '%s' in ref '%s' of repository '%s'! "
@@ -725,15 +750,9 @@ struct GitInputScheme : InputScheme
725750
// the remainder
726751
}
727752

753+
have_rev:
728754
auto repo = GitRepo::openRepo(repoDir);
729755

730-
auto isShallow = repo->isShallow();
731-
732-
if (isShallow && !getShallowAttr(input))
733-
throw Error(
734-
"'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified",
735-
repoInfo.locationToArg());
736-
737756
// FIXME: check whether rev is an ancestor of ref?
738757

739758
auto rev = *input.getRev();
@@ -744,10 +763,16 @@ struct GitInputScheme : InputScheme
744763
if (!input.attrs.contains("lastModified"))
745764
input.attrs.insert_or_assign("lastModified", getLastModified(settings, repoInfo, repoDir, rev));
746765

747-
if (!getShallowAttr(input)) {
748-
/* Like lastModified, skip revCount if supplied by the caller. */
749-
if (!input.attrs.contains("revCount"))
750-
input.attrs.insert_or_assign("revCount", getRevCount(settings, repoInfo, repoDir, rev));
766+
/* Like lastModified, skip revCount if supplied by the caller. */
767+
if (!shallow && !input.attrs.contains("revCount")) {
768+
auto isShallow = repo->isShallow();
769+
770+
if (isShallow && !shallow)
771+
throw Error(
772+
"'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified",
773+
repoInfo.locationToArg());
774+
775+
input.attrs.insert_or_assign("revCount", getRevCount(settings, repoInfo, repoDir, rev));
751776
}
752777

753778
printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.locationToArg());

tests/functional/flakes/common.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ writeSimpleFlake() {
3232
baseName = builtins.baseNameOf ./.;
3333
3434
root = ./.;
35+
36+
number = 123;
3537
};
3638
}
3739
EOF

tests/functional/flakes/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ suites += {
3636
'trace-ifd.sh',
3737
'build-time-flake-inputs.sh',
3838
'substitution.sh',
39+
'shallow.sh',
3940
],
4041
'workdir' : meson.current_source_dir(),
4142
}

tests/functional/flakes/shallow.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env bash
2+
3+
export _NIX_FORCE_HTTP=1
4+
5+
source ./common.sh
6+
7+
requireGit
8+
TODO_NixOS
9+
10+
createFlake1
11+
12+
repoDir="$TEST_ROOT/repo"
13+
mkdir -p "$repoDir"
14+
echo "# foo" >> "$flake1Dir/flake.nix"
15+
git -C "$flake1Dir" commit -a -m bla
16+
17+
cat > "$repoDir"/flake.nix <<EOF
18+
{
19+
inputs.dep = {
20+
type = "git";
21+
url = "file://$flake1Dir";
22+
};
23+
outputs = inputs: rec {
24+
revs = assert inputs.dep.number == 123; inputs.dep.revCount;
25+
};
26+
}
27+
EOF
28+
29+
# This will do a non-shallow fetch.
30+
[[ $(nix eval "path:$repoDir#revs") = 2 ]]
31+
32+
# This should re-use the existing non-shallow clone.
33+
clearStore
34+
mv "$flake1Dir" "$flake1Dir.moved"
35+
[[ $(nix eval "path:$repoDir#revs") = 2 ]]

tests/nixos/fetch-git/test-cases/fetchTree-shallow/default.nix

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
description = "fetchTree fetches git repos shallowly by default";
2+
description = "fetchTree fetches git repos shallowly if possible";
33
script = ''
44
# purge nix git cache to make sure we start with a clean slate
55
client.succeed("rm -rf ~/.cache/nix")
@@ -28,6 +28,7 @@
2828
type = "git";
2929
url = "{repo.remote}";
3030
rev = "{commit2_rev}";
31+
revCount = 1234;
3132
}}
3233
"""
3334

0 commit comments

Comments
 (0)