1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-25 10:41:16 +02:00

GitSourceAccessor: Make thread-safe

This commit is contained in:
Eelco Dolstra 2025-06-04 21:37:17 +02:00
parent 4751cbef63
commit 4bce2d723d

View file

@ -665,28 +665,40 @@ ref<GitRepo> GitRepo::openRepo(const std::filesystem::path & path, bool create,
struct GitSourceAccessor : SourceAccessor struct GitSourceAccessor : SourceAccessor
{ {
ref<GitRepoImpl> repo; struct State
Object root; {
std::optional<lfs::Fetch> lfsFetch = std::nullopt; ref<GitRepoImpl> repo;
Object root;
std::optional<lfs::Fetch> lfsFetch = std::nullopt;
};
Sync<State> state_;
GitSourceAccessor(ref<GitRepoImpl> repo_, const Hash & rev, bool smudgeLfs) GitSourceAccessor(ref<GitRepoImpl> repo_, const Hash & rev, bool smudgeLfs)
: repo(repo_) : state_{
, root(peelToTreeOrBlob(lookupObject(*repo, hashToOID(rev)).get())) State {
.repo = repo_,
.root = peelToTreeOrBlob(lookupObject(*repo_, hashToOID(rev)).get()),
.lfsFetch = smudgeLfs ? std::make_optional(lfs::Fetch(*repo_, hashToOID(rev))) : std::nullopt,
}
}
{ {
if (smudgeLfs)
lfsFetch = std::make_optional(lfs::Fetch(*repo, hashToOID(rev)));
} }
std::string readBlob(const CanonPath & path, bool symlink) std::string readBlob(const CanonPath & path, bool symlink)
{ {
const auto blob = getBlob(path, symlink); auto state(state_.lock());
if (lfsFetch) { const auto blob = getBlob(*state, path, symlink);
if (lfsFetch->shouldFetch(path)) {
if (state->lfsFetch) {
if (state->lfsFetch->shouldFetch(path)) {
StringSink s; StringSink s;
try { try {
// FIXME: do we need to hold the state lock while
// doing this?
auto contents = std::string((const char *) git_blob_rawcontent(blob.get()), git_blob_rawsize(blob.get())); auto contents = std::string((const char *) git_blob_rawcontent(blob.get()), git_blob_rawsize(blob.get()));
lfsFetch->fetch(contents, path, s, [&s](uint64_t size){ s.s.reserve(size); }); state->lfsFetch->fetch(contents, path, s, [&s](uint64_t size){ s.s.reserve(size); });
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, "while smudging git-lfs file '%s'", path); e.addTrace({}, "while smudging git-lfs file '%s'", path);
throw; throw;
@ -705,15 +717,18 @@ struct GitSourceAccessor : SourceAccessor
bool pathExists(const CanonPath & path) override bool pathExists(const CanonPath & path) override
{ {
return path.isRoot() ? true : (bool) lookup(path); auto state(state_.lock());
return path.isRoot() ? true : (bool) lookup(*state, path);
} }
std::optional<Stat> maybeLstat(const CanonPath & path) override std::optional<Stat> maybeLstat(const CanonPath & path) override
{ {
if (path.isRoot()) auto state(state_.lock());
return Stat { .type = git_object_type(root.get()) == GIT_OBJECT_TREE ? tDirectory : tRegular };
auto entry = lookup(path); if (path.isRoot())
return Stat { .type = git_object_type(state->root.get()) == GIT_OBJECT_TREE ? tDirectory : tRegular };
auto entry = lookup(*state, path);
if (!entry) if (!entry)
return std::nullopt; return std::nullopt;
@ -741,6 +756,8 @@ struct GitSourceAccessor : SourceAccessor
DirEntries readDirectory(const CanonPath & path) override DirEntries readDirectory(const CanonPath & path) override
{ {
auto state(state_.lock());
return std::visit(overloaded { return std::visit(overloaded {
[&](Tree tree) { [&](Tree tree) {
DirEntries res; DirEntries res;
@ -758,7 +775,7 @@ struct GitSourceAccessor : SourceAccessor
[&](Submodule) { [&](Submodule) {
return DirEntries(); return DirEntries();
} }
}, getTree(path)); }, getTree(*state, path));
} }
std::string readLink(const CanonPath & path) override std::string readLink(const CanonPath & path) override
@ -772,7 +789,9 @@ struct GitSourceAccessor : SourceAccessor
*/ */
std::optional<Hash> getSubmoduleRev(const CanonPath & path) std::optional<Hash> getSubmoduleRev(const CanonPath & path)
{ {
auto entry = lookup(path); auto state(state_.lock());
auto entry = lookup(*state, path);
if (!entry || git_tree_entry_type(entry) != GIT_OBJECT_COMMIT) if (!entry || git_tree_entry_type(entry) != GIT_OBJECT_COMMIT)
return std::nullopt; return std::nullopt;
@ -783,7 +802,7 @@ struct GitSourceAccessor : SourceAccessor
std::unordered_map<CanonPath, TreeEntry> lookupCache; std::unordered_map<CanonPath, TreeEntry> lookupCache;
/* Recursively look up 'path' relative to the root. */ /* Recursively look up 'path' relative to the root. */
git_tree_entry * lookup(const CanonPath & path) git_tree_entry * lookup(State & state, const CanonPath & path)
{ {
auto i = lookupCache.find(path); auto i = lookupCache.find(path);
if (i != lookupCache.end()) return i->second.get(); if (i != lookupCache.end()) return i->second.get();
@ -793,7 +812,7 @@ struct GitSourceAccessor : SourceAccessor
auto name = path.baseName().value(); auto name = path.baseName().value();
auto parentTree = lookupTree(*parent); auto parentTree = lookupTree(state, *parent);
if (!parentTree) return nullptr; if (!parentTree) return nullptr;
auto count = git_tree_entrycount(parentTree->get()); auto count = git_tree_entrycount(parentTree->get());
@ -822,29 +841,29 @@ struct GitSourceAccessor : SourceAccessor
return res; return res;
} }
std::optional<Tree> lookupTree(const CanonPath & path) std::optional<Tree> lookupTree(State & state, const CanonPath & path)
{ {
if (path.isRoot()) { if (path.isRoot()) {
if (git_object_type(root.get()) == GIT_OBJECT_TREE) if (git_object_type(state.root.get()) == GIT_OBJECT_TREE)
return dupObject<Tree>((git_tree *) &*root); return dupObject<Tree>((git_tree *) &*state.root);
else else
return std::nullopt; return std::nullopt;
} }
auto entry = lookup(path); auto entry = lookup(state, path);
if (!entry || git_tree_entry_type(entry) != GIT_OBJECT_TREE) if (!entry || git_tree_entry_type(entry) != GIT_OBJECT_TREE)
return std::nullopt; return std::nullopt;
Tree tree; Tree tree;
if (git_tree_entry_to_object((git_object * *) (git_tree * *) Setter(tree), *repo, entry)) if (git_tree_entry_to_object((git_object * *) (git_tree * *) Setter(tree), *state.repo, entry))
throw Error("looking up directory '%s': %s", showPath(path), git_error_last()->message); throw Error("looking up directory '%s': %s", showPath(path), git_error_last()->message);
return tree; return tree;
} }
git_tree_entry * need(const CanonPath & path) git_tree_entry * need(State & state, const CanonPath & path)
{ {
auto entry = lookup(path); auto entry = lookup(state, path);
if (!entry) if (!entry)
throw Error("'%s' does not exist", showPath(path)); throw Error("'%s' does not exist", showPath(path));
return entry; return entry;
@ -852,16 +871,16 @@ struct GitSourceAccessor : SourceAccessor
struct Submodule { }; struct Submodule { };
std::variant<Tree, Submodule> getTree(const CanonPath & path) std::variant<Tree, Submodule> getTree(State & state, const CanonPath & path)
{ {
if (path.isRoot()) { if (path.isRoot()) {
if (git_object_type(root.get()) == GIT_OBJECT_TREE) if (git_object_type(state.root.get()) == GIT_OBJECT_TREE)
return dupObject<Tree>((git_tree *) &*root); return dupObject<Tree>((git_tree *) &*state.root);
else else
throw Error("Git root object '%s' is not a directory", *git_object_id(root.get())); throw Error("Git root object '%s' is not a directory", *git_object_id(state.root.get()));
} }
auto entry = need(path); auto entry = need(state, path);
if (git_tree_entry_type(entry) == GIT_OBJECT_COMMIT) if (git_tree_entry_type(entry) == GIT_OBJECT_COMMIT)
return Submodule(); return Submodule();
@ -870,16 +889,16 @@ struct GitSourceAccessor : SourceAccessor
throw Error("'%s' is not a directory", showPath(path)); throw Error("'%s' is not a directory", showPath(path));
Tree tree; Tree tree;
if (git_tree_entry_to_object((git_object * *) (git_tree * *) Setter(tree), *repo, entry)) if (git_tree_entry_to_object((git_object * *) (git_tree * *) Setter(tree), *state.repo, entry))
throw Error("looking up directory '%s': %s", showPath(path), git_error_last()->message); throw Error("looking up directory '%s': %s", showPath(path), git_error_last()->message);
return tree; return tree;
} }
Blob getBlob(const CanonPath & path, bool expectSymlink) Blob getBlob(State & state, const CanonPath & path, bool expectSymlink)
{ {
if (!expectSymlink && git_object_type(root.get()) == GIT_OBJECT_BLOB) if (!expectSymlink && git_object_type(state.root.get()) == GIT_OBJECT_BLOB)
return dupObject<Blob>((git_blob *) &*root); return dupObject<Blob>((git_blob *) &*state.root);
auto notExpected = [&]() auto notExpected = [&]()
{ {
@ -892,7 +911,7 @@ struct GitSourceAccessor : SourceAccessor
if (path.isRoot()) notExpected(); if (path.isRoot()) notExpected();
auto entry = need(path); auto entry = need(state, path);
if (git_tree_entry_type(entry) != GIT_OBJECT_BLOB) if (git_tree_entry_type(entry) != GIT_OBJECT_BLOB)
notExpected(); notExpected();
@ -907,7 +926,7 @@ struct GitSourceAccessor : SourceAccessor
} }
Blob blob; Blob blob;
if (git_tree_entry_to_object((git_object * *) (git_blob * *) Setter(blob), *repo, entry)) if (git_tree_entry_to_object((git_object * *) (git_blob * *) Setter(blob), *state.repo, entry))
throw Error("looking up file '%s': %s", showPath(path), git_error_last()->message); throw Error("looking up file '%s': %s", showPath(path), git_error_last()->message);
return blob; return blob;