diff --git a/src/libfetchers/fetch-settings.hh b/src/libfetchers/fetch-settings.hh index f095963a834..d6aa04dcc6b 100644 --- a/src/libfetchers/fetch-settings.hh +++ b/src/libfetchers/fetch-settings.hh @@ -70,6 +70,9 @@ struct FetchSettings : public Config Setting warnDirty{this, true, "warn-dirty", "Whether to warn about dirty Git/Mercurial trees."}; + Setting includeUntrackedFiles{this, false, "include-untracked-files", + "Whether to include untracked files in Git/Mercurial trees."}; + Setting flakeRegistry{this, "https://channels.nixos.org/flake-registry.json", "flake-registry", R"( Path or URI of the global flake registry. diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index f554dcc5f35..28da365089e 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -257,7 +257,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this return (*((std::function *) payload))(path, statusFlags); } - WorkdirInfo getWorkdirInfo() override + WorkdirInfo getWorkdirInfo(bool includeUntrackedFiles) override { WorkdirInfo info; @@ -284,6 +284,10 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this git_status_options options = GIT_STATUS_OPTIONS_INIT; options.flags |= GIT_STATUS_OPT_INCLUDE_UNMODIFIED; options.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES; + if (includeUntrackedFiles) { + options.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + } if (git_status_foreach_ext(*this, &options, &statusCallbackTrampoline, &statusCallback)) throw Error("getting working directory status: %s", git_error_last()->message); diff --git a/src/libfetchers/git-utils.hh b/src/libfetchers/git-utils.hh index 1def82071ef..dbfc87df4d5 100644 --- a/src/libfetchers/git-utils.hh +++ b/src/libfetchers/git-utils.hh @@ -48,7 +48,7 @@ struct GitRepo std::vector submodules; }; - virtual WorkdirInfo getWorkdirInfo() = 0; + virtual WorkdirInfo getWorkdirInfo(bool includeUntrackedFiles) = 0; /* Get the ref that HEAD points to. */ virtual std::optional getWorkdirRef() = 0; diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 7208a0b6dbe..9b172178ee9 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -386,7 +386,7 @@ struct GitInputScheme : InputScheme // If this is a local directory and no ref or revision is // given, then allow the use of an unclean working tree. if (!input.getRef() && !input.getRev() && repoInfo.isLocal) - repoInfo.workdirInfo = GitRepo::openRepo(CanonPath(repoInfo.url))->getWorkdirInfo(); + repoInfo.workdirInfo = GitRepo::openRepo(CanonPath(repoInfo.url))->getWorkdirInfo(fetchSettings.includeUntrackedFiles); return repoInfo; } diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc index 9244acf3982..27d1ef20626 100644 --- a/src/libfetchers/mercurial.cc +++ b/src/libfetchers/mercurial.cc @@ -175,7 +175,10 @@ struct MercurialInputScheme : InputScheme if (!input.getRef() && !input.getRev() && isLocal && pathExists(actualUrl + "/.hg")) { - bool clean = runHg({ "status", "-R", actualUrl, "--modified", "--added", "--removed" }) == ""; + Strings hgStatusOptions = { "status", "-R", actualUrl, "--modified", "--added", "--removed" }; + if (fetchSettings.includeUntrackedFiles) + hgStatusOptions.push_back("--unknown"); + bool clean = runHg(hgStatusOptions) == ""; if (!clean) { @@ -190,8 +193,11 @@ struct MercurialInputScheme : InputScheme input.attrs.insert_or_assign("ref", chomp(runHg({ "branch", "-R", actualUrl }))); + hgStatusOptions = { "status", "-R", actualUrl, "--clean", "--modified", "--added", "--no-status", "--print0" }; + if (fetchSettings.includeUntrackedFiles) + hgStatusOptions.push_back("--unknown"); auto files = tokenizeString>( - runHg({ "status", "-R", actualUrl, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s); + runHg(hgStatusOptions), "\0"s); Path actualPath(absPath(actualUrl)); diff --git a/tests/functional/fetchGit.sh b/tests/functional/fetchGit.sh index c38cd27eb81..079e1c59199 100644 --- a/tests/functional/fetchGit.sh +++ b/tests/functional/fetchGit.sh @@ -106,6 +106,25 @@ path2=$(nix eval --impure --raw --expr "(builtins.fetchGit $repo).outPath") [ ! -e $path2/.git ] [[ $(cat $path2/dir1/foo) = foo ]] +# Using an unclean tree with --include-git-untracked-files option should yield the tracked and uncommitted changes +path21=$(nix eval --include-untracked-files --impure --raw --expr "(builtins.fetchGit $repo).outPath") +[ ! -e $path21/hello ] +[ -e $path21/bar ] +[ -e $path21/dir2/bar ] +[ ! -e $path21/.git ] +[[ $(cat $path21/dir1/foo) = foo ]] + +# Using an unclean tree with --include-git-untracked-files option should take into account .gitignore files +echo "/bar" >> $repo/.gitignore +echo "bar" >> $repo/dir2/.gitignore +path22=$(nix eval --include-untracked-files --impure --raw --expr "(builtins.fetchGit $repo).outPath") +[ ! -e $path22/hello ] +[ ! -e $path22/bar ] +[ ! -e $path22/dir2/bar ] +[ ! -e $path22/.git ] +git -C $repo checkout -- $repo/.gitignore +git -C $repo clean -fd + [[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).rev") = 0000000000000000000000000000000000000000 ]] [[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).dirtyRev") = "${rev2}-dirty" ]] [[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).dirtyShortRev") = "${rev2:0:7}-dirty" ]] diff --git a/tests/functional/fetchMercurial.sh b/tests/functional/fetchMercurial.sh index e6f8525c6f7..b39677b2a20 100644 --- a/tests/functional/fetchMercurial.sh +++ b/tests/functional/fetchMercurial.sh @@ -1,6 +1,6 @@ source common.sh -[[ $(type -p hq) ]] || skipTest "Mercurial not installed" +[[ $(type -p hg) ]] || skipTest "Mercurial not installed" clearStore @@ -88,6 +88,22 @@ path2=$(nix eval --impure --raw --expr "(builtins.fetchMercurial $repo).outPath" [ ! -e $path2/.hg ] [[ $(cat $path2/dir1/foo) = foo ]] +path21=$(nix eval --include-untracked-files --impure --raw --expr "(builtins.fetchMercurial $repo).outPath") +[ ! -e $path21/hello ] +[ -e $path21/bar ] +[ -e $path21/dir2/bar ] +[ ! -e $path21/.hg ] +[[ $(cat $path21/dir1/foo) = foo ]] + +echo "bar" >> $repo/.hgignore +path22=$(nix eval --include-untracked-files --impure --raw --expr "(builtins.fetchMercurial $repo).outPath") +[ ! -e $path22/hello ] +[ ! -e $path22/bar ] +[ ! -e $path22/dir2/bar ] +[ ! -e $path22/.git ] +sed -i '/bar/d' $repo/.hgignore +hg status --cwd $repo + [[ $(nix eval --impure --raw --expr "(builtins.fetchMercurial $repo).rev") = 0000000000000000000000000000000000000000 ]] # ... unless we're using an explicit ref. diff --git a/tests/functional/flakes/mercurial.sh b/tests/functional/flakes/mercurial.sh index 0622c79b7b4..7074af6f7d4 100644 --- a/tests/functional/flakes/mercurial.sh +++ b/tests/functional/flakes/mercurial.sh @@ -1,6 +1,6 @@ source ./common.sh -[[ $(type -p hq) ]] || skipTest "Mercurial not installed" +[[ $(type -p hg) ]] || skipTest "Mercurial not installed" flake1Dir=$TEST_ROOT/flake-hg1 mkdir -p $flake1Dir