// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/other_tools/root_maps/commit_git_map.hpp" #include #include #include #include #include "fmt/core.h" #include "src/buildtool/common/artifact.hpp" #include "src/buildtool/common/artifact_digest.hpp" #include "src/buildtool/common/artifact_digest_factory.hpp" #include "src/buildtool/crypto/hash_function.hpp" #include "src/buildtool/file_system/file_root.hpp" #include "src/buildtool/file_system/file_system_manager.hpp" #include "src/buildtool/file_system/git_cas.hpp" #include "src/buildtool/file_system/git_repo.hpp" #include "src/buildtool/file_system/git_types.hpp" #include "src/buildtool/file_system/object_type.hpp" #include "src/buildtool/multithreading/task_system.hpp" #include "src/buildtool/progress_reporting/task_tracker.hpp" #include "src/buildtool/storage/fs_utils.hpp" #include "src/other_tools/git_operations/git_ops_types.hpp" #include "src/other_tools/git_operations/git_repo_remote.hpp" #include "src/utils/cpp/expected.hpp" #include "src/utils/cpp/path.hpp" #include "src/utils/cpp/tmp_dir.hpp" namespace { [[nodiscard]] auto GitURLIsPath(std::string const& url) noexcept -> std::optional { static auto const kAbsPath = std::string{"/"}; static auto const kRelPath = std::string{"./"}; static auto const kFileScheme = std::string{"file://"}; if (url.starts_with(kAbsPath) or url.starts_with(kRelPath)) { return ToNormalPath(url).string(); } if (url.starts_with(kFileScheme)) { return ToNormalPath(url.substr(kFileScheme.length())).string(); } return std::nullopt; } [[nodiscard]] auto IsCacheGitRoot( StorageConfig const& native_storage_config, std::filesystem::path const& repo_root) noexcept -> bool { return std::filesystem::absolute(ToNormalPath(repo_root)) == std::filesystem::absolute( ToNormalPath(native_storage_config.GitRoot())); } /// \brief Helper function for ensuring the serve endpoint, if given, has the /// root if it was marked absent. /// It guarantees the logger is called exactly once with fatal on failure, and /// the setter on success. void EnsureRootAsAbsent(std::string const& tree_id, std::filesystem::path const& repo_root, GitRepoInfo const& repo_info, ServeApi const* serve, CommitGitMap::SetterPtr const& ws_setter, CommitGitMap::LoggerPtr const& logger) { // this is an absent root if (serve != nullptr) { // check if the serve endpoint has this root auto const has_tree = serve->CheckRootTree(tree_id); if (not has_tree) { (*logger)(fmt::format("Checking that the serve endpoint knows tree " "{} failed.", tree_id), /*fatal=*/true); return; } if (not *has_tree) { // try to see if serve endpoint has the information to prepare the // root itself auto const serve_result = serve->RetrieveTreeFromCommit(repo_info.hash, repo_info.subdir, /*sync_tree = */ false); if (serve_result) { // if serve has set up the tree, it must match what we expect auto const& served_tree_id = serve_result->tree; if (tree_id != served_tree_id) { (*logger)(fmt::format("Mismatch in served root tree " "id:\nexpected {}, but got {}", tree_id, served_tree_id), /*fatal=*/true); return; } } else { // check if serve failure was due to commit not being found or // it is otherwise fatal if (serve_result.error() == GitLookupError::Fatal) { (*logger)(fmt::format("Serve endpoint failed to set up " "root from known commit {}", repo_info.hash), /*fatal=*/true); return; } auto digest = ArtifactDigestFactory::Create(HashFunction::Type::GitSHA1, tree_id, /*size_unknown=*/0, /*is_tree=*/true); if (not digest.has_value()) { (*logger)(std::move(digest).error(), /*fatal=*/true); return; } // the tree is known locally, so we can upload it to remote CAS // for the serve endpoint to retrieve it and set up the root auto uploaded = serve->UploadTree(*digest, repo_root); if (not uploaded.has_value()) { (*logger)(std::move(uploaded).error().Message(), /*fatal=*/true); return; } } } } else { // give warning (*logger)(fmt::format("Workspace root {} marked absent but no serve " "endpoint provided.", tree_id), /*fatal=*/false); } // set root as absent (*ws_setter)(std::pair( nlohmann::json::array({repo_info.ignore_special ? FileRoot::kGitTreeIgnoreSpecialMarker : FileRoot::kGitTreeMarker, tree_id}), /*is_cache_hit=*/false)); } /// \brief Helper function for improved readability. /// It guarantees the logger is called exactly once with fatal on failure, and /// the setter on success. void WriteIdFileAndSetWSRoot(std::string const& root_tree_id, std::string const& subdir, bool ignore_special, StorageConfig const& native_storage_config, GitCASPtr const& git_cas, std::filesystem::path const& tree_id_file, CommitGitMap::SetterPtr const& ws_setter, CommitGitMap::LoggerPtr const& logger) { // write association of the root tree in id file if (not StorageUtils::WriteTreeIDFile(tree_id_file, root_tree_id)) { (*logger)(fmt::format("Failed to write tree id {} to file {}", root_tree_id, tree_id_file.string()), /*fatal=*/true); return; } // extract the subdir tree auto git_repo = GitRepoRemote::Open(git_cas); // link fake repo to odb if (not git_repo) { (*logger)(fmt::format("Could not open cache object database {}", native_storage_config.GitRoot().string()), /*fatal=*/true); return; } auto wrapped_logger = std::make_shared( [logger, subdir, tree = root_tree_id](auto const& msg, bool fatal) { (*logger)(fmt::format("While getting subdir {} in tree {}:\n{}", subdir, tree, msg), fatal); }); auto tree_id = git_repo->GetSubtreeFromTree(root_tree_id, subdir, wrapped_logger); if (not tree_id) { return; } // set the workspace root as present (*ws_setter)(std::pair( nlohmann::json::array({ignore_special ? FileRoot::kGitTreeIgnoreSpecialMarker : FileRoot::kGitTreeMarker, *tree_id, native_storage_config.GitRoot().string()}), false)); } void TagAndSetRoot(std::filesystem::path const& repo_root, GitRepoInfo const& repo_info, bool finish_task, GitCASPtr const& git_cas, gsl::not_null const& critical_git_op_map, gsl::not_null const& progress, gsl::not_null const& ts, CommitGitMap::SetterPtr const& ws_setter, CommitGitMap::LoggerPtr const& logger) { GitOpKey op_key = {.params = { repo_root, // target_path repo_info.hash, // git_hash "Keep referenced tree alive" // message }, .op_type = GitOpType::KEEP_TAG}; critical_git_op_map->ConsumeAfterKeysReady( ts, {std::move(op_key)}, [git_cas, repo_info, repo_root, progress, ws_setter, logger, finish_task](auto const& values) { GitOpValue op_result = *values[0]; // check flag if (not op_result.result) { (*logger)("Keep tag failed", /*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; } // setup wrapped logger auto wrapped_logger = std::make_shared( [logger](auto const& msg, bool fatal) { (*logger)( fmt::format("While getting subtree from commit:\n{}", msg), fatal); }); // get tree id and return workspace root auto res = git_repo->GetSubtreeFromCommit( repo_info.hash, repo_info.subdir, wrapped_logger); if (not res) { return; } // set the workspace root as present if (finish_task) { progress->TaskTracker().Stop(repo_info.origin); } (*ws_setter)( std::pair(nlohmann::json::array( {repo_info.ignore_special ? FileRoot::kGitTreeIgnoreSpecialMarker : FileRoot::kGitTreeMarker, *std::move(res), // subtree id repo_root}), /*is_cache_hit=*/false)); }, [logger, target_path = repo_root](auto const& msg, bool fatal) { (*logger)(fmt::format("While running critical Git op KEEP_TAG " "for target {}:\n{}", target_path.string(), msg), fatal); }); } void TakeCommitFromOlderGeneration( std::filesystem::path const& source, gsl::not_null const& native_storage_config, std::filesystem::path const& repo_root, GitRepoInfo const& repo_info, GitCASPtr const& git_cas, gsl::not_null const& critical_git_op_map, gsl::not_null const& progress, gsl::not_null const& ts, CommitGitMap::SetterPtr const& ws_setter, CommitGitMap::LoggerPtr const& logger) { GitOpKey op_key = {.params = { source, // target_path repo_info.hash, // git_hash "Tag commit for fetching" // message }, .op_type = GitOpType::KEEP_TAG}; critical_git_op_map->ConsumeAfterKeysReady( ts, {std::move(op_key)}, [logger, git_cas, repo_root, native_storage_config, source, repo_info, critical_git_op_map, progress, ts, ws_setter](auto const& values) { GitOpValue op_result = *values[0]; if (not op_result.result) { (*logger)("Keep tag failed", /*fatal=*/true); return; } auto tag = *op_result.result; 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; } auto fetch_logger = std::make_shared( [logger, tag, source](auto const& msg, bool fatal) { (*logger)(fmt::format("While fetching {} from {}:\n{}", tag, source.string(), msg), fatal); }); if (not git_repo->LocalFetchViaTmpRepo( *native_storage_config, source, tag, fetch_logger)) { return; } TagAndSetRoot(repo_root, repo_info, false, git_cas, critical_git_op_map, progress, ts, ws_setter, logger); }, [logger, source, hash = repo_info.hash](auto const& msg, bool fatal) { (*logger)(fmt::format("While tagging {} in {} for fetching:\n{}", source.string(), hash, msg), fatal); }); } void NetworkFetchAndSetPresentRoot( GitRepoInfo const& repo_info, std::filesystem::path const& repo_root, std::string const& fetch_repo, MirrorsPtr const& additional_mirrors, GitCASPtr const& git_cas, StorageConfig const& native_storage_config, gsl::not_null const& critical_git_op_map, std::string const& git_bin, std::vector const& launcher, bool fetch_absent, gsl::not_null const& progress, gsl::not_null const& ts, CommitGitMap::SetterPtr const& ws_setter, CommitGitMap::LoggerPtr const& logger) { // reaching here can only result in a root that is present if (repo_info.absent and not fetch_absent) { (*logger)( fmt::format("Cannot create workspace root as absent for commit {}.", repo_info.hash), /*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; } // store failed attempts for subsequent logging bool fetched{false}; std::string err_messages{}; // keep all remotes checked to report them in case fetch fails std::string remotes_buffer{}; // try repo url auto all_mirrors = std::vector({fetch_repo}); // try repo mirrors afterwards all_mirrors.insert( all_mirrors.end(), repo_info.mirrors.begin(), repo_info.mirrors.end()); if (auto preferred_hostnames = MirrorsUtils::GetPreferredHostnames(additional_mirrors); not preferred_hostnames.empty()) { all_mirrors = MirrorsUtils::SortByHostname(all_mirrors, preferred_hostnames); } // always try local mirrors first auto local_mirrors = MirrorsUtils::GetLocalMirrors(additional_mirrors, fetch_repo); all_mirrors.insert( all_mirrors.begin(), local_mirrors.begin(), local_mirrors.end()); auto inherit_env = MirrorsUtils::GetInheritEnv(additional_mirrors, repo_info.inherit_env); for (auto mirror : all_mirrors) { auto mirror_path = GitURLIsPath(mirror); if (mirror_path) { mirror = std::filesystem::absolute(*mirror_path).string(); } auto wrapped_logger = std::make_shared( [mirror, &err_messages](auto const& msg, bool /*fatal*/) { err_messages += fmt::format( "While attempting fetch from URL {}:\n{}\n", mirror, msg); }); if (git_repo->FetchViaTmpRepo(native_storage_config, mirror, repo_info.branch, inherit_env, git_bin, launcher, wrapped_logger)) { fetched = true; break; } // add local mirror to buffer remotes_buffer.append(fmt::format("\n> {}", mirror)); } if (not fetched) { // log fetch failure and list the remotes tried (*logger)( fmt::format("While trying to fetch from provided remotes:\n{}Fetch " "failed for the provided remotes{}", err_messages, remotes_buffer), /*fatal=*/true); return; } // setup wrapped logger auto wrapped_logger = std::make_shared( [logger](auto const& msg, bool fatal) { (*logger)(fmt::format("While checking commit exists:\n{}", msg), fatal); }); // check if commit exists now, after fetch auto is_commit_present = git_repo->CheckCommitExists(repo_info.hash, wrapped_logger); if (not is_commit_present) { return; } if (not *is_commit_present) { // commit could not be fetched, so fail (*logger)(fmt::format( "Could not fetch commit {} from branch {} for remote {}", repo_info.hash, repo_info.branch, fetch_repo), /*fatal=*/true); return; } // if witnessing repository is the Git cache, then also tag the commit if (IsCacheGitRoot(native_storage_config, repo_root)) { TagAndSetRoot(repo_root, repo_info, true, git_cas, critical_git_op_map, progress, ts, ws_setter, logger); } else { 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; } // setup wrapped logger auto wrapped_logger = std::make_shared( [logger](auto const& msg, bool fatal) { (*logger)( fmt::format("While getting subtree from commit:\n{}", msg), fatal); }); // get tree id and return workspace root auto res = git_repo->GetSubtreeFromCommit( repo_info.hash, repo_info.subdir, wrapped_logger); if (not res) { return; } // set the workspace root as present progress->TaskTracker().Stop(repo_info.origin); (*ws_setter)(std::pair( nlohmann::json::array({repo_info.ignore_special ? FileRoot::kGitTreeIgnoreSpecialMarker : FileRoot::kGitTreeMarker, *std::move(res), // subtree id repo_root}), /*is_cache_hit=*/false)); } } /// \brief Contains the main logic for this async map. It ensures the commit is /// available for processing (including fetching for a present root) and setting /// the root. /// It guarantees the logger is called exactly once with fatal on failure, and /// the setter on success. void EnsureCommit( GitRepoInfo const& repo_info, std::filesystem::path const& repo_root, std::string const& fetch_repo, MirrorsPtr const& additional_mirrors, GitCASPtr const& git_cas, gsl::not_null const& critical_git_op_map, gsl::not_null const& import_to_git_map, std::string const& git_bin, std::vector const& launcher, ServeApi const* serve, gsl::not_null const& native_storage_config, gsl::not_null const& local_api, IExecutionApi const* remote_api, bool fetch_absent, gsl::not_null const& progress, gsl::not_null const& ts, CommitGitMap::SetterPtr const& ws_setter, CommitGitMap::LoggerPtr const& logger) { // link fake repo to odb auto git_repo = GitRepoRemote::Open(git_cas); if (not git_repo) { (*logger)( fmt::format("Could not open repository {}", repo_root.string()), /*fatal=*/true); return; } // setup wrapped logger auto wrapped_logger = std::make_shared( [logger](auto const& msg, bool fatal) { (*logger)(fmt::format("While checking commit exists:\n{}", msg), fatal); }); auto is_commit_present = git_repo->CheckCommitExists(repo_info.hash, wrapped_logger); if (is_commit_present == std::nullopt) { return; } if (not is_commit_present.value()) { auto tree_id_file = StorageUtils::GetCommitTreeIDFile( *native_storage_config, repo_info.hash); // Check if we have stored a file association between commit and tree; // if an association file exists, the respective tree MUST be in the // Git cache if (FileSystemManager::Exists(tree_id_file)) { // read resolved tree id auto resolved_tree_id = FileSystemManager::ReadFile(tree_id_file); if (not resolved_tree_id) { (*logger)(fmt::format("Failed to read tree id from file {}", tree_id_file.string()), /*fatal=*/true); return; } auto just_git_cas = GitCAS::Open(native_storage_config->GitRoot()); if (not just_git_cas) { (*logger)( fmt::format("Could not open Git cache database {}", native_storage_config->GitRoot().string()), /*fatal=*/true); return; } auto just_git_repo = GitRepo::Open(just_git_cas); if (not just_git_repo) { (*logger)( fmt::format("Could not open Git cache repository {}", native_storage_config->GitRoot().string()), /*fatal=*/true); return; } // extract the subdir tree wrapped_logger = std::make_shared( [logger, subdir = repo_info.subdir, tree = *resolved_tree_id]( auto const& msg, bool fatal) { (*logger)( fmt::format("While getting subdir {} in tree {}:\n{}", subdir, tree, msg), fatal); }); auto tree_id = just_git_repo->GetSubtreeFromTree( *resolved_tree_id, repo_info.subdir, wrapped_logger); if (not tree_id) { return; } // set the workspace root if (repo_info.absent and not fetch_absent) { // try by all available means to generate & set the absent root EnsureRootAsAbsent( *tree_id, native_storage_config->GitRoot(), /*repo_root*/ repo_info, serve, ws_setter, logger); } else { // this root is present (*ws_setter)( std::pair(nlohmann::json::array( {repo_info.ignore_special ? FileRoot::kGitTreeIgnoreSpecialMarker : FileRoot::kGitTreeMarker, *tree_id, native_storage_config->GitRoot().string()}), /*is_cache_hit=*/false)); } // done! return; } // Check older generations for presence of the commit for (std::size_t generation = 1; generation < native_storage_config->num_generations; generation++) { auto old = native_storage_config->GitGenerationRoot(generation); if (FileSystemManager::IsDirectory(old)) { auto old_repo = GitRepo::Open(old); auto no_logging = std::make_shared( [](auto /*unused*/, auto /*unused*/) {}); if (old_repo) { auto check_result = old_repo->CheckCommitExists(repo_info.hash, no_logging); if (check_result and *check_result) { TakeCommitFromOlderGeneration(old, native_storage_config, repo_root, repo_info, git_cas, critical_git_op_map, progress, ts, ws_setter, logger); return; } } } } // TODO(aehlig): should we also check for commit-tree association in // older generations // Not present locally, we have to fetch progress->TaskTracker().Start(repo_info.origin); // check if commit is known to remote serve service if (serve != nullptr) { // if root purely absent, request only the subdir tree if (repo_info.absent and not fetch_absent) { auto const serve_result = serve->RetrieveTreeFromCommit(repo_info.hash, repo_info.subdir, /*sync_tree = */ false); if (serve_result) { // set the workspace root as absent progress->TaskTracker().Stop(repo_info.origin); (*ws_setter)(std::pair( nlohmann::json::array( {repo_info.ignore_special ? FileRoot::kGitTreeIgnoreSpecialMarker : FileRoot::kGitTreeMarker, serve_result->tree}), /*is_cache_hit=*/false)); return; } // check if serve failure was due to commit not being found or // it is otherwise fatal if (serve_result.error() == GitLookupError::Fatal) { (*logger)(fmt::format("Serve endpoint failed to set up " "root from known commit {}", repo_info.hash), /*fatal=*/true); return; } } // otherwise, request (and sync) the whole commit tree, to ensure // we maintain the id file association else { auto const serve_result = serve->RetrieveTreeFromCommit(repo_info.hash, /*subdir = */ ".", /*sync_tree = */ true); if (serve_result) { auto const root_tree_id = serve_result->tree; auto const remote_digest = serve_result->digest; // verify if we know the tree already in the local Git cache GitOpKey op_key = {.params = { native_storage_config ->GitRoot(), // target_path "", // git_hash std::nullopt, // message std::nullopt, // source_path true // init_bare }, .op_type = GitOpType::ENSURE_INIT}; critical_git_op_map->ConsumeAfterKeysReady( ts, {std::move(op_key)}, [root_tree_id, remote_digest, tree_id_file, repo_info, repo_root, fetch_repo, additional_mirrors, native_storage_config, git_cas, critical_git_op_map, import_to_git_map, git_bin, launcher, local_api, remote_api, fetch_absent, progress, ts, ws_setter, logger](auto const& values) { GitOpValue op_result = *values[0]; // check flag if (not op_result.result) { (*logger)("Git init failed", /*fatal=*/true); return; } auto just_git_repo = GitRepoRemote::Open(op_result.git_cas); if (not just_git_repo) { (*logger)( fmt::format("Could not open Git " "cache repository " "{}", native_storage_config->GitRoot() .string()), /*fatal=*/true); return; } // check tree existence auto wrapped_logger = std::make_shared( [logger, native_storage_config, tree = root_tree_id](auto const& msg, bool fatal) { (*logger)( fmt::format( "While verifying presence of " "tree {} in repository {}:\n{}", tree, native_storage_config->GitRoot() .string(), msg), fatal); }); auto tree_present = just_git_repo->CheckTreeExists( root_tree_id, wrapped_logger); if (not tree_present) { return; } if (*tree_present) { progress->TaskTracker().Stop(repo_info.origin); // write association to id file, get subdir // tree, and set the workspace root as present WriteIdFileAndSetWSRoot( root_tree_id, repo_info.subdir, repo_info.ignore_special, *native_storage_config, op_result.git_cas, tree_id_file, ws_setter, logger); return; } // now check if the tree is in the local checkout, // if this checkout is not our Git cache; this can // save an unnecessary remote CAS call if (not IsCacheGitRoot(*native_storage_config, repo_root)) { auto git_repo = GitRepoRemote::Open(git_cas); if (not git_repo) { (*logger)(fmt::format("Could not open Git " "repository {}", repo_root.string()), /*fatal=*/true); return; } // check tree existence wrapped_logger = std::make_shared( [logger, tree = root_tree_id, repo_root](auto const& msg, bool fatal) { (*logger)( fmt::format( "While verifying presence " "of tree {} in repository " "{}:\n{}", tree, repo_root.string(), msg), fatal); }); tree_present = git_repo->CheckTreeExists( root_tree_id, wrapped_logger); if (not tree_present) { return; } if (*tree_present) { progress->TaskTracker().Stop( repo_info.origin); // get subdir tree and set the workspace // root as present; as this tree is not in // our Git cache, no file association should // be stored wrapped_logger = std::make_shared< AsyncMapConsumerLogger>( [logger, subdir = repo_info.subdir, tree = root_tree_id](auto const& msg, bool fatal) { (*logger)( fmt::format( "While getting subdir {} " "in tree {}:\n{}", subdir, tree, msg), fatal); }); auto tree_id = git_repo->GetSubtreeFromTree( root_tree_id, repo_info.subdir, wrapped_logger); if (not tree_id) { return; } // set the workspace root as present (*ws_setter)(std::pair( nlohmann::json::array( {repo_info.ignore_special ? FileRoot:: kGitTreeIgnoreSpecialMarker : FileRoot::kGitTreeMarker, *tree_id, repo_root.string()}), false)); // done! return; } } // try to get root tree from remote CAS; use the // digest received from serve; whether native or // compatible, it will either way be imported to Git if (remote_api != nullptr and remote_digest and remote_api->RetrieveToCas( {Artifact::ObjectInfo{ .digest = *remote_digest, .type = ObjectType::Tree}}, *local_api)) { progress->TaskTracker().Stop(repo_info.origin); // Move tree from local CAS to local Git storage auto tmp_dir = native_storage_config->CreateTypedTmpDir( "fetch-absent-root"); if (not tmp_dir) { (*logger)( fmt::format( "Failed to create tmp directory " "after fetching root tree {} for " "absent commit {}", root_tree_id, repo_info.hash), /*fatal=*/true); return; } if (not local_api->RetrieveToPaths( {Artifact::ObjectInfo{ .digest = *remote_digest, .type = ObjectType::Tree}}, {tmp_dir->GetPath()})) { (*logger)(fmt::format( "Failed to copy fetched root " "tree {} to {}", root_tree_id, tmp_dir->GetPath().string()), /*fatal=*/true); return; } CommitInfo c_info{ tmp_dir->GetPath(), "tree", root_tree_id}; import_to_git_map->ConsumeAfterKeysReady( ts, {std::move(c_info)}, [tmp_dir, // keep tmp_dir alive root_tree_id, native_storage_config, subdir = repo_info.subdir, ignore_special = repo_info.ignore_special, just_git_cas = op_result.git_cas, tree_id_file, ws_setter, logger](auto const& values) { if (not values[0]->second) { (*logger)("Importing to git failed", /*fatal=*/true); return; } // sanity check: we should get the // expected tree if (values[0]->first != root_tree_id) { (*logger)( fmt::format( "Mismatch in imported git " "tree id:\nexpected {}, " "but got {}", root_tree_id, values[0]->first), /*fatal=*/true); return; } // tree is now in Git cache; // write association to id file, get // subdir tree, and set the workspace // root as present WriteIdFileAndSetWSRoot( root_tree_id, subdir, ignore_special, *native_storage_config, just_git_cas, tree_id_file, ws_setter, logger); }, [logger, tmp_dir, root_tree_id]( auto const& msg, bool fatal) { (*logger)( fmt::format( "While moving root tree {} " "from {} to local git:\n{}", root_tree_id, tmp_dir->GetPath().string(), msg), fatal); }); return; } // just serve did not make the tree available in // the remote CAS, so fall back to network NetworkFetchAndSetPresentRoot( repo_info, repo_root, fetch_repo, additional_mirrors, git_cas, *native_storage_config, critical_git_op_map, git_bin, launcher, fetch_absent, progress, ts, ws_setter, logger); }, [logger, target_path = native_storage_config->GitRoot()]( auto const& msg, bool fatal) { (*logger)(fmt::format("While running critical Git " "op ENSURE_INIT bare for " "target {}:\n{}", target_path.string(), msg), fatal); }); // done! return; } // check if serve failure was due to commit not being found // or it is otherwise fatal if (serve_result.error() == GitLookupError::Fatal) { (*logger)(fmt::format("Serve endpoint failed to set up " "root from known commit {}", repo_info.hash), /*fatal=*/true); return; } } } NetworkFetchAndSetPresentRoot(repo_info, repo_root, fetch_repo, additional_mirrors, git_cas, *native_storage_config, critical_git_op_map, git_bin, launcher, fetch_absent, progress, ts, ws_setter, logger); } else { // commit is present in given repository progress->TaskTracker().Start(repo_info.origin); // setup wrapped logger auto wrapped_logger = std::make_shared( [logger](auto const& msg, bool fatal) { (*logger)( fmt::format("While getting subtree from commit:\n{}", msg), fatal); }); // get tree id and return workspace root auto res = git_repo->GetSubtreeFromCommit( repo_info.hash, repo_info.subdir, wrapped_logger); if (not res) { return; } auto subtree = *std::move(res); // set the workspace root if (repo_info.absent and not fetch_absent) { // try by all available means to generate and set the absent root EnsureRootAsAbsent( subtree, repo_root, repo_info, serve, ws_setter, logger); } else { // set root as present (*ws_setter)( std::pair(nlohmann::json::array( {repo_info.ignore_special ? FileRoot::kGitTreeIgnoreSpecialMarker : FileRoot::kGitTreeMarker, subtree, repo_root.string()}), /*is_cache_hit=*/true)); } } } } // namespace /// \brief Create a CommitGitMap object auto CreateCommitGitMap( gsl::not_null const& critical_git_op_map, gsl::not_null const& import_to_git_map, LocalPathsPtr const& just_mr_paths, MirrorsPtr const& additional_mirrors, std::string const& git_bin, std::vector const& launcher, ServeApi const* serve, gsl::not_null const& native_storage_config, gsl::not_null const& local_api, IExecutionApi const* remote_api, bool fetch_absent, gsl::not_null const& progress, std::size_t jobs) -> CommitGitMap { auto commit_to_git = [critical_git_op_map, import_to_git_map, just_mr_paths, additional_mirrors, git_bin, launcher, serve, native_storage_config, local_api, remote_api, fetch_absent, progress](auto ts, auto setter, auto logger, auto /* unused */, auto const& key) { // get root for repo (making sure that if repo is a path, it is // absolute) std::string fetch_repo = key.repo_url; auto fetch_repo_path = GitURLIsPath(fetch_repo); if (fetch_repo_path) { fetch_repo = std::filesystem::absolute(*fetch_repo_path).string(); } std::filesystem::path repo_root = StorageUtils::GetGitRoot( *native_storage_config, just_mr_paths, fetch_repo); // ensure git repo // define Git operation to be done GitOpKey op_key = { .params = { repo_root, // target_path "", // git_hash std::nullopt, // message std::nullopt, // source_path not just_mr_paths->git_checkout_locations.contains( fetch_repo) // init_bare }, .op_type = GitOpType::ENSURE_INIT}; critical_git_op_map->ConsumeAfterKeysReady( ts, {std::move(op_key)}, [key, repo_root, fetch_repo, additional_mirrors, critical_git_op_map, import_to_git_map, git_bin, launcher, serve, native_storage_config, local_api, remote_api, fetch_absent, progress, 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; } // setup a wrapped_logger auto wrapped_logger = std::make_shared( [logger, target_path = repo_root](auto const& msg, bool fatal) { (*logger)(fmt::format("While ensuring commit for " "repository {}:\n{}", target_path.string(), msg), fatal); }); EnsureCommit(key, repo_root, fetch_repo, additional_mirrors, op_result.git_cas, critical_git_op_map, import_to_git_map, git_bin, launcher, serve, native_storage_config, local_api, remote_api, fetch_absent, progress, ts, setter, wrapped_logger); }, [logger, target_path = repo_root](auto const& msg, bool fatal) { (*logger)(fmt::format("While running critical Git op " "ENSURE_INIT for target {}:\n{}", target_path.string(), msg), fatal); }); }; return AsyncMapConsumer>( commit_to_git, jobs); }