diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 6d53b3f82..dfb1cabda 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -507,13 +507,13 @@ EvalState::~EvalState() void EvalState::allowPath(const Path & path) { if (auto rootFS2 = rootFS.dynamic_pointer_cast()) - rootFS2->allowPath(CanonPath(path)); + rootFS2->allowPrefix(CanonPath(path)); } void EvalState::allowPath(const StorePath & storePath) { if (auto rootFS2 = rootFS.dynamic_pointer_cast()) - rootFS2->allowPath(CanonPath(store->toRealPath(storePath))); + rootFS2->allowPrefix(CanonPath(store->toRealPath(storePath))); } void EvalState::allowAndSetStorePathString(const StorePath & storePath, Value & v) diff --git a/src/libfetchers/filtering-input-accessor.cc b/src/libfetchers/filtering-input-accessor.cc index 581ce3c1d..8a08bc35b 100644 --- a/src/libfetchers/filtering-input-accessor.cc +++ b/src/libfetchers/filtering-input-accessor.cc @@ -51,33 +51,33 @@ void FilteringInputAccessor::checkAccess(const CanonPath & path) struct AllowListInputAccessorImpl : AllowListInputAccessor { - std::set allowedPaths; + std::set allowedPrefixes; AllowListInputAccessorImpl( ref next, - std::set && allowedPaths, + std::set && allowedPrefixes, MakeNotAllowedError && makeNotAllowedError) : AllowListInputAccessor(SourcePath(next), std::move(makeNotAllowedError)) - , allowedPaths(std::move(allowedPaths)) + , allowedPrefixes(std::move(allowedPrefixes)) { } bool isAllowed(const CanonPath & path) override { - return path.isAllowed(allowedPaths); + return path.isAllowed(allowedPrefixes); } - void allowPath(CanonPath path) override + void allowPrefix(CanonPath prefix) override { - allowedPaths.insert(std::move(path)); + allowedPrefixes.insert(std::move(prefix)); } }; ref AllowListInputAccessor::create( ref next, - std::set && allowedPaths, + std::set && allowedPrefixes, MakeNotAllowedError && makeNotAllowedError) { - return make_ref(next, std::move(allowedPaths), std::move(makeNotAllowedError)); + return make_ref(next, std::move(allowedPrefixes), std::move(makeNotAllowedError)); } bool CachingFilteringInputAccessor::isAllowed(const CanonPath & path) diff --git a/src/libfetchers/filtering-input-accessor.hh b/src/libfetchers/filtering-input-accessor.hh index 8a9b206ee..8111a72c5 100644 --- a/src/libfetchers/filtering-input-accessor.hh +++ b/src/libfetchers/filtering-input-accessor.hh @@ -54,18 +54,19 @@ struct FilteringInputAccessor : InputAccessor }; /** - * A wrapping `InputAccessor` that checks paths against an allow-list. + * A wrapping `InputAccessor` that checks paths against a set of + * allowed prefixes. */ struct AllowListInputAccessor : public FilteringInputAccessor { /** - * Grant access to the specified path. + * Grant access to the specified prefix. */ - virtual void allowPath(CanonPath path) = 0; + virtual void allowPrefix(CanonPath prefix) = 0; static ref create( ref next, - std::set && allowedPaths, + std::set && allowedPrefixes, MakeNotAllowedError && makeNotAllowedError); using FilteringInputAccessor::FilteringInputAccessor; diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 382a363f0..8fcfc4b0d 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -2,6 +2,7 @@ #include "fs-input-accessor.hh" #include "input-accessor.hh" #include "filtering-input-accessor.hh" +#include "memory-input-accessor.hh" #include "cache.hh" #include "finally.hh" #include "processes.hh" @@ -750,17 +751,21 @@ ref GitRepoImpl::getAccessor(const Hash & rev, bool exportIgnore) ref GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError) { auto self = ref(shared_from_this()); + /* In case of an empty workdir, return an empty in-memory tree. We + cannot use AllowListInputAccessor because it would return an + error for the root (and we can't add the root to the allow-list + since that would allow access to all its children). */ ref fileAccessor = - AllowListInputAccessor::create( - makeFSInputAccessor(path), - std::set { wd.files }, - std::move(makeNotAllowedError)); - if (exportIgnore) { + wd.files.empty() + ? makeEmptyInputAccessor() + : AllowListInputAccessor::create( + makeFSInputAccessor(path), + std::set { wd.files }, + std::move(makeNotAllowedError)).cast(); + if (exportIgnore) return make_ref(self, fileAccessor, std::nullopt); - } - else { + else return fileAccessor; - } } std::vector> GitRepoImpl::getSubmodules(const Hash & rev, bool exportIgnore) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index f9a1cb1bc..8a156b43f 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -158,6 +158,8 @@ std::vector getPublicKeys(const Attrs & attrs) } // end namespace +static const Hash nullRev{HashAlgorithm::SHA1}; + struct GitInputScheme : InputScheme { std::optional inputFromURL(const ParsedURL & url, bool requireTree) const override @@ -708,10 +710,12 @@ struct GitInputScheme : InputScheme if (auto ref = repo->getWorkdirRef()) input.attrs.insert_or_assign("ref", *ref); - auto rev = repoInfo.workdirInfo.headRev.value(); + /* Return a rev of 000... if there are no commits yet. */ + auto rev = repoInfo.workdirInfo.headRev.value_or(nullRev); input.attrs.insert_or_assign("rev", rev.gitRev()); - input.attrs.insert_or_assign("revCount", getRevCount(repoInfo, repoInfo.url, rev)); + input.attrs.insert_or_assign("revCount", + rev == nullRev ? 0 : getRevCount(repoInfo, repoInfo.url, rev)); verifyCommit(input, repo); } else { diff --git a/src/libfetchers/memory-input-accessor.cc b/src/libfetchers/memory-input-accessor.cc index 88a2e34e8..34a801f67 100644 --- a/src/libfetchers/memory-input-accessor.cc +++ b/src/libfetchers/memory-input-accessor.cc @@ -20,4 +20,10 @@ ref makeMemoryInputAccessor() return make_ref(); } +ref makeEmptyInputAccessor() +{ + static auto empty = makeMemoryInputAccessor().cast(); + return empty; +} + } diff --git a/src/libfetchers/memory-input-accessor.hh b/src/libfetchers/memory-input-accessor.hh index 508b07722..63afadd2a 100644 --- a/src/libfetchers/memory-input-accessor.hh +++ b/src/libfetchers/memory-input-accessor.hh @@ -13,4 +13,6 @@ struct MemoryInputAccessor : InputAccessor ref makeMemoryInputAccessor(); +ref makeEmptyInputAccessor(); + } diff --git a/tests/functional/fetchGit.sh b/tests/functional/fetchGit.sh index 4b7e8c2c8..8b965e831 100644 --- a/tests/functional/fetchGit.sh +++ b/tests/functional/fetchGit.sh @@ -271,3 +271,28 @@ git -C "$repo" add hello .gitignore git -C "$repo" commit -m 'Bla1' cd "$repo" path11=$(nix eval --impure --raw --expr "(builtins.fetchGit ./.).outPath") + +# Test a workdir with no commits. +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 ]] + +echo foo > "$empty/x" + +[[ $(nix eval --impure --expr "builtins.removeAttrs (builtins.fetchGit $empty) [\"outPath\"]") = $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; }' ]] + +# Test a repo with an empty commit. +git -C "$empty" rm -f x + +git -C "$empty" config user.email "foobar@example.com" +git -C "$empty" config user.name "Foobar" +git -C "$empty" commit --allow-empty --allow-empty-message --message "" + +nix eval --impure --expr "let attrs = builtins.fetchGit $empty; in assert attrs.lastModified != 0; assert attrs.rev != \"0000000000000000000000000000000000000000\"; assert attrs.revCount == 1; true"