diff options
author | Paul Cristian Sarbu <paul.cristian.sarbu@huawei.com> | 2024-04-05 11:48:13 +0200 |
---|---|---|
committer | Paul Cristian Sarbu <paul.cristian.sarbu@huawei.com> | 2024-04-10 14:06:21 +0200 |
commit | 4fe5fc8aec6e391a5e300e234bdc41375bff1d9e (patch) | |
tree | d5eb3cb68f5d44fc89f885667cf6242b0d137a99 | |
parent | 43ac36b4cd2b6aa0b42219f5920a7b4e72832ae5 (diff) | |
download | justbuild-4fe5fc8aec6e391a5e300e234bdc41375bff1d9e.tar.gz |
resolve_symlinks_map: Allow separate source and target repositories
In certain cases, e.g., on the serve endpoint, an unresolved tree
might lie in a repository other than the Git cache, therefore we
cannot create any new entries there, as it would violate our
guarantee that we only write under our local build root.
Therefore, the resolve_symlinks_map now receives pointers to both
the source and target Git databases and ensures that:
1. any tree created on-the-fly is stored exclusively in the target
repository, and
2. any other entry required for those trees is made available in
the target repository by copying it from the source repository.
Note that in our use case the target repository is always our Git
cache and passing a pointer to that object database is done to
avoid the overhead of otherwise opening the database very often.
7 files changed, 275 insertions, 92 deletions
diff --git a/src/buildtool/file_system/symlinks_map/TARGETS b/src/buildtool/file_system/symlinks_map/TARGETS index 125a4f52..0d958168 100644 --- a/src/buildtool/file_system/symlinks_map/TARGETS +++ b/src/buildtool/file_system/symlinks_map/TARGETS @@ -5,6 +5,7 @@ , "srcs": ["resolve_symlinks_map.cpp"] , "deps": [ "pragma_special" + , ["src/buildtool/file_system", "git_cas"] , ["src/buildtool/file_system", "git_repo"] , ["src/buildtool/file_system", "object_type"] , ["src/buildtool/multithreading", "async_map_consumer"] @@ -14,7 +15,11 @@ ] , "stage": ["src", "buildtool", "file_system", "symlinks_map"] , "private-deps": - [["@", "fmt", "", "fmt"], ["src/buildtool/storage", "config"]] + [ ["@", "fmt", "", "fmt"] + , ["src/buildtool/file_system", "object_type"] + , ["src/buildtool/storage", "config"] + , ["src/utils/cpp", "gsl"] + ] } , "pragma_special": { "type": ["@", "rules", "CC", "library"] diff --git a/src/buildtool/file_system/symlinks_map/resolve_symlinks_map.cpp b/src/buildtool/file_system/symlinks_map/resolve_symlinks_map.cpp index 5c9dd89b..198629bb 100644 --- a/src/buildtool/file_system/symlinks_map/resolve_symlinks_map.cpp +++ b/src/buildtool/file_system/symlinks_map/resolve_symlinks_map.cpp @@ -15,19 +15,104 @@ #include "src/buildtool/file_system/symlinks_map/resolve_symlinks_map.hpp" #include "fmt/core.h" -#include "src/buildtool/file_system/git_repo.hpp" +#include "src/buildtool/file_system/object_type.hpp" #include "src/buildtool/storage/config.hpp" +#include "src/utils/cpp/gsl.hpp" namespace { +/// \brief Ensures that a given blob is in the target repo. +/// On errors, calls logger with fatal and returns false. +[[nodiscard]] auto EnsureBlobExists(GitObjectToResolve const& obj, + GitRepo::TreeEntryInfo const& entry_info, + ResolveSymlinksMap::LoggerPtr const& logger) + -> bool { + ExpectsAudit(IsBlobObject(entry_info.type)); + // check if entry is in target repo + auto target_git_repo = GitRepo::Open(obj.target_cas); + if (not target_git_repo) { + (*logger)("ResolveSymlinks: could not open target Git repository!", + /*fatal=*/true); + return false; + } + auto wrapped_logger = std::make_shared<GitRepo::anon_logger_t>( + [logger, id = entry_info.id](auto const& msg, bool fatal) { + (*logger)(fmt::format("ResolveSymlinks: while checking blob {} " + "exists in target Git repository:\n{}", + id, + msg), + fatal); + }); + auto has_blob = + target_git_repo->CheckBlobExists(entry_info.id, wrapped_logger); + if (not has_blob) { + return false; + } + if (not *has_blob) { + // copy blob from source repo to target repo, if source is not target + if (obj.source_cas.get() == obj.target_cas.get()) { + (*logger)( + fmt::format("ResolveSymlinks: unexpectedly missing blob {} in " + "both source and target Git repositories", + entry_info.id), + /*fatal=*/true); + return false; + } + auto source_git_repo = GitRepo::Open(obj.source_cas); + if (not source_git_repo) { + (*logger)("ResolveSymlinks: could not open source Git repository", + /*fatal=*/true); + return false; + } + wrapped_logger = std::make_shared<GitRepo::anon_logger_t>( + [logger, id = entry_info.id](auto const& msg, bool fatal) { + (*logger)(fmt::format("ResolveSymlinks: while checking blob {} " + "exists in source Git repository:\n{}", + id, + msg), + fatal); + }); + auto res = source_git_repo->TryReadBlob(entry_info.id, wrapped_logger); + if (not res.first) { + return false; // fatal failure + } + if (not res.second.has_value()) { + (*logger)(fmt::format("ResolveSymlinks: unexpectedly missing " + "blob {} in source Git repository", + entry_info.id), + /*fatal=*/true); + return false; + } + // write blob in target repository + wrapped_logger = std::make_shared<GitRepo::anon_logger_t>( + [logger, id = entry_info.id](auto const& msg, bool fatal) { + (*logger)(fmt::format("ResolveSymlinks: while writing blob " + "{} into Git cache:\n{}", + id, + msg), + fatal); + }); + if (not target_git_repo->WriteBlob(res.second.value(), + wrapped_logger)) { + return false; + } + } + return true; // success! +} + +/// \brief Method to handle entries by their known type. +/// Guarantees to either call logger with fatal or call setter on returning. void ResolveKnownEntry(GitObjectToResolve const& obj, GitRepo::TreeEntryInfo const& entry_info, - GitCASPtr const& just_git_cas, ResolveSymlinksMap::SetterPtr const& setter, ResolveSymlinksMap::LoggerPtr const& logger, ResolveSymlinksMap::SubCallerPtr const& subcaller) { // differentiated treatment based on object type if (IsFileObject(entry_info.type)) { + // ensure target repository has the entry + if (not EnsureBlobExists(obj, entry_info, logger)) { + return; + } // files are already resolved, so return the hash directly (*setter)(ResolvedGitObject{.id = entry_info.id, .type = entry_info.type, @@ -36,13 +121,13 @@ void ResolveKnownEntry(GitObjectToResolve const& obj, else if (IsTreeObject(entry_info.type)) { // for tree types we resolve by rebuilding the tree from the // resolved children - auto just_git_repo = GitRepo::Open(just_git_cas); - if (not just_git_repo) { - (*logger)("ResolveSymlinks: could not open Git cache repository!", + auto source_git_repo = GitRepo::Open(obj.source_cas); + if (not source_git_repo) { + (*logger)("ResolveSymlinks: could not open source Git repository!", /*fatal=*/true); return; } - auto children = just_git_repo->ReadTree( + auto children = source_git_repo->ReadTree( entry_info.id, [](std::vector<bazel_re::Digest> const& /*unused*/) { return true; @@ -66,7 +151,7 @@ void ResolveKnownEntry(GitObjectToResolve const& obj, obj.pragma_special != PragmaSpecial::Ignore) { // children info is known, so pass this forward if (IsSymlinkObject(e.type)) { - if (auto target = just_git_cas->ReadObject(raw_id)) { + if (auto target = obj.source_cas->ReadObject(raw_id)) { children_info.emplace_back( obj.root_tree_id, obj.rel_path / e.name, @@ -74,7 +159,9 @@ void ResolveKnownEntry(GitObjectToResolve const& obj, std::make_optional(GitRepo::TreeEntryInfo{ .id = ToHexString(raw_id), .type = e.type, - .symlink_content = *target})); + .symlink_content = *target}), + obj.source_cas, + obj.target_cas); } else { (*logger)( @@ -94,14 +181,16 @@ void ResolveKnownEntry(GitObjectToResolve const& obj, GitRepo::TreeEntryInfo{ .id = ToHexString(raw_id), .type = e.type, - .symlink_content = std::nullopt}); + .symlink_content = std::nullopt}, + obj.source_cas, + obj.target_cas); } } } } (*subcaller)( children_info, - [children_info, parent = obj, just_git_cas, setter, logger]( + [children_info, parent = obj, setter, logger]( auto const& resolved_entries) { // create the entries map of the children GitRepo::tree_entries_t entries{}; @@ -114,18 +203,18 @@ void ResolveKnownEntry(GitObjectToResolve const& obj, p.filename().string(), // we only need the name resolved_entries[i]->type); } - // create the tree inside our Git CAS, which is already - // existing by this point. Also, this operation is - // guarded internally, so no need for the - // critical_git_op map - auto just_git_repo = GitRepo::Open(just_git_cas); - if (not just_git_repo) { + // create the tree inside target repo, which should already be + // existing. This operation is guarded internally, so no need + // for extra locking + auto target_git_repo = GitRepo::Open(parent.target_cas); + if (not target_git_repo) { (*logger)( - "ResolveSymlinks: could not open Git cache repository!", + "ResolveSymlinks: could not open target Git " + "repository!", /*fatal=*/true); return; } - auto tree_raw_id = just_git_repo->CreateTree(entries); + auto tree_raw_id = target_git_repo->CreateTree(entries); if (not tree_raw_id) { (*logger)(fmt::format("ResolveSymlinks: failed to create " "resolved tree {} in root tree {}", @@ -170,9 +259,13 @@ void ResolveKnownEntry(GitObjectToResolve const& obj, /*fatal=*/true); return; } - // if partially resolved, return non-upwards symlinks as-is + // if resolving partially, return a non-upwards symlink as-is if (obj.pragma_special == PragmaSpecial::ResolvePartially and PathIsNonUpwards(*entry_info.symlink_content)) { + // ensure target repository has the entry + if (not EnsureBlobExists(obj, entry_info, logger)) { + return; + } // return as symlink object (*setter)(ResolvedGitObject{.id = entry_info.id, .type = ObjectType::Symlink, @@ -186,7 +279,9 @@ void ResolveKnownEntry(GitObjectToResolve const& obj, {GitObjectToResolve(obj.root_tree_id, n_target, obj.pragma_special, - /*known_info=*/std::nullopt)}, + /*known_info=*/std::nullopt, + obj.source_cas, + obj.target_cas)}, [setter](auto const& values) { (*setter)(ResolvedGitObject{*values[0]}); }, @@ -202,31 +297,26 @@ auto CreateResolveSymlinksMap() -> ResolveSymlinksMap { auto logger, auto subcaller, auto const& key) { - // look up entry by its relative path - auto just_git_cas = GitCAS::Open(StorageConfig::GitRoot()); - if (not just_git_cas) { - (*logger)("ResolveSymlinks: could not open Git cache database!", - /*fatal=*/true); - return; - } - auto just_git_repo = GitRepo::Open(just_git_cas); - if (not just_git_repo) { - (*logger)("ResolveSymlinks: could not open Git cache repository!", - /*fatal=*/true); - return; + auto entry_info = key.known_info; + if (not entry_info) { + // look up entry by its relative path inside root tree if not known + auto source_git_repo = GitRepo::Open(key.source_cas); + if (not source_git_repo) { + (*logger)( + "ResolveSymlinks: could not open source Git repository!", + /*fatal=*/true); + return; + } + entry_info = source_git_repo->GetObjectByPathFromTree( + key.root_tree_id, key.rel_path); } - auto entry_info = key.known_info - ? key.known_info - : just_git_repo->GetObjectByPathFromTree( - key.root_tree_id, key.rel_path); // differentiate between existing path and non-existing if (entry_info) { - ResolveKnownEntry( - key, *entry_info, just_git_cas, setter, logger, subcaller); + ResolveKnownEntry(key, *entry_info, setter, logger, subcaller); } else { - // non-existing paths come from symlinks, so treat accordingly + // non-existing paths come from symlinks, so treat accordingly; // sanity check: pragma ignore special should not be set if here if (key.pragma_special == PragmaSpecial::Ignore) { (*logger)( @@ -251,11 +341,12 @@ auto CreateResolveSymlinksMap() -> ResolveSymlinksMap { {GitObjectToResolve(key.root_tree_id, parent_path, key.pragma_special, - /*known_info=*/std::nullopt)}, + /*known_info=*/std::nullopt, + key.source_cas, + key.target_cas)}, [key, parent_path, filename = key.rel_path.filename(), - just_git_cas, setter, logger, subcaller](auto const& values) { @@ -271,24 +362,25 @@ auto CreateResolveSymlinksMap() -> ResolveSymlinksMap { return; } // check if filename exists in resolved parent tree - auto just_git_repo = GitRepo::Open(just_git_cas); - if (not just_git_repo) { + auto target_git_repo = GitRepo::Open(key.target_cas); + if (not target_git_repo) { (*logger)( "ResolveSymlinks: could not open Git cache " "repository!", /*fatal=*/true); return; } - auto entry_info = just_git_repo->GetObjectByPathFromTree( + auto entry_info = target_git_repo->GetObjectByPathFromTree( resolved_parent.id, filename); if (entry_info) { ResolveKnownEntry( GitObjectToResolve(key.root_tree_id, resolved_parent.path / filename, key.pragma_special, - /*known_info=*/std::nullopt), + /*known_info=*/std::nullopt, + key.source_cas, + key.target_cas), std::move(*entry_info), - just_git_cas, setter, logger, subcaller); diff --git a/src/buildtool/file_system/symlinks_map/resolve_symlinks_map.hpp b/src/buildtool/file_system/symlinks_map/resolve_symlinks_map.hpp index 41986c23..fc706268 100644 --- a/src/buildtool/file_system/symlinks_map/resolve_symlinks_map.hpp +++ b/src/buildtool/file_system/symlinks_map/resolve_symlinks_map.hpp @@ -22,6 +22,7 @@ #include <string> #include <utility> // std::move +#include "src/buildtool/file_system/git_cas.hpp" #include "src/buildtool/file_system/git_repo.hpp" #include "src/buildtool/file_system/object_type.hpp" #include "src/buildtool/file_system/symlinks_map/pragma_special.hpp" @@ -31,7 +32,9 @@ #include "src/utils/cpp/path_hash.hpp" /// \brief Information needed to resolve an object (blob or tree) given its -/// path relative to the path of a root tree in a given CAS. +/// path relative to the path of a root tree in a given CAS. The unresolved +/// entries should be available in the specified source Git repository, and the +/// resolved entries being made available in the target Git repository. struct GitObjectToResolve { // hash of the root tree std::string root_tree_id{}; /* key */ @@ -42,17 +45,28 @@ struct GitObjectToResolve { // sometimes the info of the object at the required path is already known, // so leverage this to avoid extra work std::optional<GitRepo::TreeEntryInfo> known_info{std::nullopt}; + // object db to use as source of unresolved entries; it is guaranteed that + // this repository is treated as read-only if it differs from target_cas + GitCASPtr source_cas{}; + // object db to use as target for resolved entries; can be the same as + // source_cas and usually it is the Git cache; as the caller has access to + // such a pointer, it reduces the overhead from opening the Git cache often + GitCASPtr target_cas{}; GitObjectToResolve() = default; // needed for cycle detection only! GitObjectToResolve(std::string root_tree_id_, std::filesystem::path const& rel_path_, PragmaSpecial const& pragma_special_, - std::optional<GitRepo::TreeEntryInfo> known_info_) + std::optional<GitRepo::TreeEntryInfo> known_info_, + GitCASPtr source_cas_, + GitCASPtr target_cas_) : root_tree_id{std::move(root_tree_id_)}, rel_path{ToNormalPath(rel_path_)}, pragma_special{pragma_special_}, - known_info{std::move(known_info_)} {}; + known_info{std::move(known_info_)}, + source_cas{std::move(source_cas_)}, + target_cas{std::move(target_cas_)} {}; [[nodiscard]] auto operator==( GitObjectToResolve const& other) const noexcept -> bool { diff --git a/src/buildtool/serve_api/serve_service/source_tree.cpp b/src/buildtool/serve_api/serve_service/source_tree.cpp index 7a4a8828..4eeadf3f 100644 --- a/src/buildtool/serve_api/serve_service/source_tree.cpp +++ b/src/buildtool/serve_api/serve_service/source_tree.cpp @@ -375,6 +375,7 @@ auto SourceTreeService::SyncArchive(std::string const& tree_id, auto SourceTreeService::ResolveContentTree( std::string const& tree_id, std::filesystem::path const& repo_path, + bool repo_is_git_cache, std::optional<PragmaSpecial> const& resolve_special, bool sync_tree, ServeArchiveTreeResponse* response) -> ::grpc::Status { @@ -396,7 +397,26 @@ auto SourceTreeService::ResolveContentTree( return SyncArchive( *resolved_tree_id, repo_path, sync_tree, response); } - // resolve tree + // resolve tree; target repository is always the Git cache + auto target_cas = GitCAS::Open(StorageConfig::GitRoot()); + if (not target_cas) { + auto str = fmt::format("Failed to open Git ODB at {}", + StorageConfig::GitRoot().string()); + logger_->Emit(LogLevel::Error, str); + response->set_status(ServeArchiveTreeResponse::INTERNAL_ERROR); + return ::grpc::Status::OK; + } + auto source_cas = target_cas; + if (not repo_is_git_cache) { + source_cas = GitCAS::Open(repo_path); + if (not source_cas) { + auto str = fmt::format("Failed to open Git ODB at {}", + repo_path.string()); + logger_->Emit(LogLevel::Error, str); + response->set_status(ServeArchiveTreeResponse::INTERNAL_ERROR); + return ::grpc::Status::OK; + } + } ResolvedGitObject resolved_tree{}; bool failed{false}; { @@ -406,7 +426,9 @@ auto SourceTreeService::ResolveContentTree( {GitObjectToResolve{tree_id, ".", *resolve_special, - /*known_info=*/std::nullopt}}, + /*known_info=*/std::nullopt, + source_cas, + target_cas}}, [&resolved_tree](auto hashes) { resolved_tree = *hashes[0]; }, [logger = logger_, tree_id, &failed](auto const& msg, bool fatal) { @@ -625,6 +647,7 @@ auto SourceTreeService::ArchiveImportToGit( } return ResolveContentTree(*subtree_id, StorageConfig::GitRoot(), + /*repo_is_git_cache=*/true, resolve_special, sync_tree, response); @@ -689,6 +712,7 @@ auto SourceTreeService::ServeArchiveTree( if (std::holds_alternative<std::string>(res)) { return ResolveContentTree(std::get<std::string>(res), // tree_id StorageConfig::GitRoot(), + /*repo_is_git_cache=*/true, resolve_special, request->sync_tree(), response); @@ -709,6 +733,7 @@ auto SourceTreeService::ServeArchiveTree( return ResolveContentTree( std::get<std::string>(res), // tree_id path, + /*repo_is_git_cache=*/false, resolve_special, request->sync_tree(), response); diff --git a/src/buildtool/serve_api/serve_service/source_tree.hpp b/src/buildtool/serve_api/serve_service/source_tree.hpp index f2585f40..d8d117e0 100644 --- a/src/buildtool/serve_api/serve_service/source_tree.hpp +++ b/src/buildtool/serve_api/serve_service/source_tree.hpp @@ -173,9 +173,12 @@ class SourceTreeService final ServeArchiveTreeResponse* response) -> ::grpc::Status; + /// \brief Resolves a tree from given repository with respect to symlinks. + /// The resolved tree will always be placed in the Git cache. [[nodiscard]] auto ResolveContentTree( std::string const& tree_id, std::filesystem::path const& repo_path, + bool repo_is_git_cache, std::optional<PragmaSpecial> const& resolve_special, bool sync_tree, ServeArchiveTreeResponse* response) -> ::grpc::Status; diff --git a/src/other_tools/root_maps/content_git_map.cpp b/src/other_tools/root_maps/content_git_map.cpp index 2a6cad56..d3b0e35b 100644 --- a/src/other_tools/root_maps/content_git_map.cpp +++ b/src/other_tools/root_maps/content_git_map.cpp @@ -171,6 +171,7 @@ void EnsureRootAsAbsent( void ResolveContentTree( ArchiveRepoInfo const& key, std::string const& tree_hash, + GitCASPtr const& just_git_cas, bool is_cache_hit, bool is_absent, bool serve_api_exists, @@ -212,13 +213,15 @@ void ResolveContentTree( } } else { - // resolve tree + // resolve tree; both source and target repos are the Git cache resolve_symlinks_map->ConsumeAfterKeysReady( ts, {GitObjectToResolve(tree_hash, ".", *key.pragma_special, - /*known_info=*/std::nullopt)}, + /*known_info=*/std::nullopt, + just_git_cas, + just_git_cas)}, [resolve_symlinks_map, tree_hash, tree_id_file, @@ -357,6 +360,7 @@ void WriteIdFileAndSetWSRoot( // resolve tree and set workspace root ResolveContentTree(key, *subtree_hash, + just_git_cas, false, /*is_cache_hit*/ is_absent, serve_api_exists, @@ -543,6 +547,7 @@ auto CreateContentGitMap( ResolveContentTree( key, *subtree_hash, + op_result.git_cas, /*is_cache_hit = */ true, /*is_absent = */ (key.absent and not fetch_absent), serve_api_exists, diff --git a/src/other_tools/root_maps/fpath_git_map.cpp b/src/other_tools/root_maps/fpath_git_map.cpp index 7f05e8b5..a38a2255 100644 --- a/src/other_tools/root_maps/fpath_git_map.cpp +++ b/src/other_tools/root_maps/fpath_git_map.cpp @@ -95,6 +95,8 @@ void ResolveFilePathTree( std::string const& target_path, std::string const& tree_hash, std::optional<PragmaSpecial> const& pragma_special, + GitCASPtr const& source_cas, + GitCASPtr const& target_cas, bool absent, gsl::not_null<ResolveSymlinksMap*> const& resolve_symlinks_map, bool serve_api_exists, @@ -117,9 +119,10 @@ void ResolveFilePathTree( return; } // if serve endpoint is given, try to ensure it has this tree - // available to be able to build against it + // available to be able to build against it; the tree is resolved, + // so it is in our Git cache CheckServeAndSetRoot(*resolved_tree_id, - repo_root, + StorageConfig::GitRoot().string(), absent, serve_api_exists, remote_api, @@ -133,10 +136,11 @@ void ResolveFilePathTree( {GitObjectToResolve(tree_hash, ".", *pragma_special, - /*known_info=*/std::nullopt)}, + /*known_info=*/std::nullopt, + source_cas, + target_cas)}, [resolve_symlinks_map, tree_hash, - repo_root, tree_id_file, absent, serve_api_exists, @@ -173,9 +177,10 @@ void ResolveFilePathTree( return; } // if serve endpoint is given, try to ensure it has this - // tree available to be able to build against it + // tree available to be able to build against it; the + // resolved tree is in the Git cache CheckServeAndSetRoot(resolved_tree.id, - repo_root, + StorageConfig::GitRoot().string(), absent, serve_api_exists, remote_api, @@ -243,22 +248,6 @@ auto CreateFilePathGitMap( return; } if (not repo_root->empty()) { // if repo root found - auto git_cas = GitCAS::Open(*repo_root); - if (not git_cas) { - (*logger)(fmt::format("Could not open object database for " - "repository {}", - repo_root->string()), - /*fatal=*/true); - return; - } - auto git_repo = - GitRepoRemote::Open(git_cas); // link fake repo to odb - if (not git_repo) { - (*logger)(fmt::format("Could not open repository {}", - repo_root->string()), - /*fatal=*/true); - return; - } // get head commit GitOpKey op_key = {.params = { @@ -273,8 +262,8 @@ auto CreateFilePathGitMap( [fpath = key.fpath, pragma_special = key.pragma_special, absent = key.absent, - git_cas = std::move(git_cas), repo_root = std::move(*repo_root), + critical_git_op_map, resolve_symlinks_map, serve_api_exists, remote_api, @@ -288,8 +277,8 @@ auto CreateFilePathGitMap( /*fatal=*/true); return; } - auto git_repo = - GitRepoRemote::Open(git_cas); // link fake repo to odb + auto git_repo = GitRepoRemote::Open( + op_result.git_cas); // link fake repo to odb if (not git_repo) { (*logger)(fmt::format("Could not open repository {}", repo_root.string()), @@ -312,18 +301,65 @@ auto CreateFilePathGitMap( if (not tree_hash) { return; } - // resolve tree and set workspace root - ResolveFilePathTree(repo_root.string(), - fpath.string(), - *tree_hash, - pragma_special, - absent, - resolve_symlinks_map, - serve_api_exists, - remote_api, - ts, - setter, - logger); + // resolve tree and set workspace root; tree gets resolved + // from source repo into the Git cache, which we first need + // to ensure is initialized + GitOpKey op_key = { + .params = + { + StorageConfig::GitRoot(), // target_path + "", // git_hash + "", // branch + std::nullopt, // message + true // init_bare + }, + .op_type = GitOpType::ENSURE_INIT}; + critical_git_op_map->ConsumeAfterKeysReady( + ts, + {std::move(op_key)}, + [repo_root, + fpath, + tree_hash, + pragma_special, + source_cas = op_result.git_cas, + absent, + resolve_symlinks_map, + serve_api_exists, + remote_api, + ts, + setter, + logger](auto const& values) { + GitOpValue op_result = *values[0]; + // check flag + if (not op_result.result) { + (*logger)("Git init failed", + /*fatal=*/true); + return; + } + ResolveFilePathTree( + repo_root.string(), + fpath.string(), + *tree_hash, + pragma_special, + source_cas, + op_result.git_cas, /*just_git_cas*/ + absent, + resolve_symlinks_map, + serve_api_exists, + remote_api, + ts, + setter, + logger); + }, + [logger, target_path = StorageConfig::GitRoot()]( + auto const& msg, bool fatal) { + (*logger)( + fmt::format("While running critical Git op " + "ENSURE_INIT for target {}:\n{}", + target_path.string(), + msg), + fatal); + }); }, [logger, target_path = *repo_root](auto const& msg, bool fatal) { @@ -388,11 +424,14 @@ auto CreateFilePathGitMap( } // we only need the tree std::string tree = values[0]->first; - // resolve tree and set workspace root + // resolve tree and set workspace root; + // we work on the Git CAS directly ResolveFilePathTree(StorageConfig::GitRoot().string(), fpath.string(), tree, pragma_special, + values[0]->second, /*source_cas*/ + values[0]->second, /*target_cas*/ absent, resolve_symlinks_map, serve_api_exists, |