From 331bf3e2613d2b1f730f3867dd32bd0dc1ca43a6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 29 Nov 2024 16:55:27 +0100 Subject: [PATCH] Git fetcher: Calculate a fingerprint for dirty workdirs This restores evaluation caching for dirty Git workdirs. --- src/libfetchers/git-utils.cc | 18 ++++++++++++++++-- src/libfetchers/git-utils.hh | 7 ++++++- src/libfetchers/git.cc | 30 +++++++++++++++++++++++++++--- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 74e68fe12..bd5786857 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -437,7 +437,12 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this { if (!(statusFlags & GIT_STATUS_INDEX_DELETED) && !(statusFlags & GIT_STATUS_WT_DELETED)) - info.files.insert(CanonPath(path)); + info.files.emplace(CanonPath(path), + statusFlags == GIT_STATUS_CURRENT + ? WorkdirInfo::State::Clean + : WorkdirInfo::State::Dirty); + else + info.deletedFiles.insert(CanonPath(path)); if (statusFlags != GIT_STATUS_CURRENT) info.isDirty = true; return 0; @@ -1202,6 +1207,15 @@ ref GitRepoImpl::getAccessor(const Hash & rev, bool exportIgnore } } +template +std::set getKeys(const std::map & c) +{ + std::set res; + for (auto & i : c) + res.insert(i.first); + return res; +} + ref GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError) { auto self = ref(shared_from_this()); @@ -1214,7 +1228,7 @@ ref GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool export ? makeEmptySourceAccessor() : AllowListSourceAccessor::create( makeFSSourceAccessor(path), - std::set { wd.files }, + std::set { getKeys(wd.files) }, std::move(makeNotAllowedError)).cast(); if (exportIgnore) return make_ref(self, fileAccessor, std::nullopt); diff --git a/src/libfetchers/git-utils.hh b/src/libfetchers/git-utils.hh index f45b5a504..12cee5db1 100644 --- a/src/libfetchers/git-utils.hh +++ b/src/libfetchers/git-utils.hh @@ -55,9 +55,14 @@ struct GitRepo in the repo yet. */ std::optional headRev; + enum State { Clean, Dirty }; + /* All files in the working directory that are unchanged, modified or added, but excluding deleted files. */ - std::set files; + std::map files; + + /* The deleted files. */ + std::set deletedFiles; /* The submodules listed in .gitmodules of this workdir. */ std::vector submodules; diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index a6883a2d3..eec134980 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -685,7 +685,7 @@ struct GitInputScheme : InputScheme if (getSubmodulesAttr(input)) /* Create mountpoints for the submodules. */ for (auto & submodule : repoInfo.workdirInfo.submodules) - repoInfo.workdirInfo.files.insert(submodule.path); + repoInfo.workdirInfo.files.emplace(submodule.path, GitRepo::WorkdirInfo::State::Clean); auto repo = GitRepo::openRepo(repoInfo.url, false, false); @@ -793,10 +793,34 @@ struct GitInputScheme : InputScheme std::optional getFingerprint(ref store, const Input & input) const override { + auto makeFingerprint = [&](const Hash & rev) + { + return rev.gitRev() + (getSubmodulesAttr(input) ? ";s" : "") + (getExportIgnoreAttr(input) ? ";e" : ""); + }; + if (auto rev = input.getRev()) - return rev->gitRev() + (getSubmodulesAttr(input) ? ";s" : "") + (getExportIgnoreAttr(input) ? ";e" : ""); - else + return makeFingerprint(*rev); + else { + auto repoInfo = getRepoInfo(input); + if (repoInfo.isLocal && repoInfo.workdirInfo.headRev) { + /* Calculate a fingerprint that takes into account the + deleted and modified/added files. */ + HashSink hashSink{HashAlgorithm::SHA512}; + for (auto & file : repoInfo.workdirInfo.files) + if (file.second == GitRepo::WorkdirInfo::State::Dirty) { + writeString("modified:", hashSink); + writeString(file.first.abs(), hashSink); + readFile(std::filesystem::path(repoInfo.url) + file.first.abs(), hashSink); + } + for (auto & file : repoInfo.workdirInfo.deletedFiles) { + writeString("deleted:", hashSink); + writeString(file.abs(), hashSink); + } + return makeFingerprint(*repoInfo.workdirInfo.headRev) + + ";d=" + hashSink.finish().first.to_string(HashFormat::Base16, false); + } return std::nullopt; + } } bool isLocked(const Input & input) const override