diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/buildtool/file_system/git_repo.cpp | 66 | ||||
-rw-r--r-- | src/buildtool/file_system/git_repo.hpp | 5 | ||||
-rw-r--r-- | src/buildtool/serve_api/serve_service/source_tree.cpp | 25 | ||||
-rw-r--r-- | src/other_tools/ops_maps/git_tree_fetch_map.cpp | 73 | ||||
-rw-r--r-- | src/other_tools/ops_maps/import_to_git_map.cpp | 44 |
5 files changed, 109 insertions, 104 deletions
diff --git a/src/buildtool/file_system/git_repo.cpp b/src/buildtool/file_system/git_repo.cpp index 2e142438..5ab010d4 100644 --- a/src/buildtool/file_system/git_repo.cpp +++ b/src/buildtool/file_system/git_repo.cpp @@ -608,60 +608,26 @@ auto GitRepo::CommitDirectory(std::filesystem::path const& dir, // share the odb lock std::shared_lock lock{GetGitCAS()->mutex_}; - // cannot perform this operation on a bare repository; this has to be - // checked because git_index_add_bypath will not do it for us! - if (not FileSystemManager::Exists(GetGitPath() / ".git")) { - (*logger)("cannot commit directory in a bare repository!", - true /*fatal*/); - return std::nullopt; - } - - // the index approach to adding entries requires them to be reachable - // from root path - auto rel_path = dir.lexically_relative(GetGitPath()); - if (rel_path.empty() or rel_path.string().starts_with("..")) { - (*logger)( - fmt::format("unsupported directory {}: not a subpath of {}", - dir.string(), - GetGitPath().string()), - true /*fatal*/); + // Due to limitations of Git in general, and libgit2 in particular, by + // which updating the index with entries that have Git-specific magic + // names is cumbersome, if at all possible, we resort to creating + // manually the tree to be commited from the given subdirectory by + // recursively creating and writing to the object database all the blobs + // and subtrees. + + // get tree containing the subdirectory entries + auto raw_id = CreateTreeFromDirectory(dir, logger); + if (not raw_id) { return std::nullopt; } - // add all files to be staged - git_index* index_ptr{nullptr}; - git_repository_index(&index_ptr, repo_->Ptr()); - auto index = std::unique_ptr<git_index, decltype(&index_closer)>( - index_ptr, index_closer); - - // due to mismanagement of .gitignore rules by libgit2 when doing a - // forced add all, we resort to using git_index_add_bypath manually for - // all entries, instead of git_index_add_all with GIT_INDEX_ADD_FORCE. - auto use_entry = [&index, rel_path](std::filesystem::path const& name, - bool is_tree) { - return is_tree or - git_index_add_bypath( - index.get(), - (rel_path / name) - .lexically_relative(".") // remove "." prefix - .c_str()) == 0; - }; - if (not FileSystemManager::ReadDirectoryEntriesRecursive( - dir, - use_entry, - /*ignored_subdirs=*/{".git"})) { - (*logger)(fmt::format("staging entries in git repository {} failed " - "with:\n{}", - GetGitPath().string(), - GitLastError()), - true /*fatal*/); - return std::nullopt; - } - // build tree from staged files + // get tree oid git_oid tree_oid; - if (git_index_write_tree(&tree_oid, index.get()) != 0) { - (*logger)(fmt::format("building tree from index in git repository " - "{} failed with:\n{}", + if (git_oid_fromraw(&tree_oid, + reinterpret_cast<unsigned char const*>( // NOLINT + raw_id->data())) != 0) { + (*logger)(fmt::format("subdir tree object id parsing in git " + "repository {} failed with:\n{}", GetGitPath().string(), GitLastError()), true /*fatal*/); diff --git a/src/buildtool/file_system/git_repo.hpp b/src/buildtool/file_system/git_repo.hpp index 2aabf7ee..4d1be53f 100644 --- a/src/buildtool/file_system/git_repo.hpp +++ b/src/buildtool/file_system/git_repo.hpp @@ -152,8 +152,9 @@ class GitRepo { using anon_logger_ptr = std::shared_ptr<anon_logger_t>; /// \brief Create tree from entries at given directory and commit it with - /// given message. Currently, the caller must guarantee that given path is - /// a subdirectory of the repository root path. + /// given message. The given path need not be a subdirectory of the + /// repository root path, but the caller must guarantee its entries are + /// readable. /// Only possible with real repository and thus non-thread-safe. /// \returns The commit hash, or nullopt if failure. It guarantees the /// logger is called exactly once with fatal if failure. diff --git a/src/buildtool/serve_api/serve_service/source_tree.cpp b/src/buildtool/serve_api/serve_service/source_tree.cpp index dd42ab83..aaa0256e 100644 --- a/src/buildtool/serve_api/serve_service/source_tree.cpp +++ b/src/buildtool/serve_api/serve_service/source_tree.cpp @@ -448,29 +448,38 @@ auto SourceTreeService::ResolveContentTree( } auto SourceTreeService::CommonImportToGit( - std::filesystem::path const& root_path, + std::filesystem::path const& content_path, std::string const& commit_message) -> expected<std::string, std::string> { + // the repository path that imports the content must be separate from the + // content path, to avoid polluting the entries + auto tmp_dir = storage_config_.CreateTypedTmpDir("import-repo"); + if (not tmp_dir) { + return unexpected{ + std::string("Failed to create tmp path for import repository")}; + } + auto const& repo_path = tmp_dir->GetPath(); // do the initial commit; no need to guard, as the tmp location is unique - auto git_repo = GitRepo::InitAndOpen(root_path, + auto git_repo = GitRepo::InitAndOpen(repo_path, /*is_bare=*/false); if (not git_repo) { return unexpected{fmt::format("Could not initialize repository {}", - root_path.string())}; + repo_path.string())}; } // wrap logger for GitRepo call std::string err; auto wrapped_logger = std::make_shared<GitRepo::anon_logger_t>( - [root_path, &err](auto const& msg, bool fatal) { + [content_path, repo_path, &err](auto const& msg, bool fatal) { if (fatal) { err = fmt::format( - "While staging and committing all in repository {}:\n{}", - root_path.string(), + "While committing directory {} in repository {}:\n{}", + content_path.string(), + repo_path.string(), msg); } }); // stage and commit all auto commit_hash = - git_repo->CommitDirectory(root_path, commit_message, wrapped_logger); + git_repo->CommitDirectory(content_path, commit_message, wrapped_logger); if (not commit_hash) { return unexpected{err}; } @@ -498,7 +507,7 @@ auto SourceTreeService::CommonImportToGit( // fetch the new commit into the Git CAS via tmp directory; the call is // thread-safe, so it needs no guarding if (not just_git_repo->LocalFetchViaTmpRepo(storage_config_, - root_path.string(), + repo_path.string(), /*branch=*/std::nullopt, wrapped_logger)) { return unexpected{err}; diff --git a/src/other_tools/ops_maps/git_tree_fetch_map.cpp b/src/other_tools/ops_maps/git_tree_fetch_map.cpp index 7a4d9e0a..95582556 100644 --- a/src/other_tools/ops_maps/git_tree_fetch_map.cpp +++ b/src/other_tools/ops_maps/git_tree_fetch_map.cpp @@ -413,8 +413,9 @@ auto CreateGitTreeFetchMap( return; } // create temporary location for command execution root - auto tmp_dir = storage_config->CreateTypedTmpDir("git-tree"); - if (not tmp_dir) { + auto content_dir = + storage_config->CreateTypedTmpDir("git-tree"); + if (not content_dir) { (*logger)( "Failed to create execution root tmp directory for " "tree id map!", @@ -444,28 +445,39 @@ auto CreateGitTreeFetchMap( } } auto const exit_code = system.Execute( - cmdline, env, tmp_dir->GetPath(), out_dir->GetPath()); + cmdline, env, content_dir->GetPath(), out_dir->GetPath()); if (not exit_code) { (*logger)(fmt::format("Failed to execute command:\n{}", nlohmann::json(cmdline).dump()), /*fatal=*/true); return; } + // create temporary location for the import repository + auto repo_dir = + storage_config->CreateTypedTmpDir("import-repo"); + if (not repo_dir) { + (*logger)( + "Failed to create tmp directory for import repository", + /*fatal=*/true); + return; + } // do an import to git with tree check - GitOpKey op_key = {.params = - { - tmp_dir->GetPath(), // target_path - "", // git_hash - fmt::format("Content of tree {}", - key.hash), // message - tmp_dir->GetPath() // source_path - }, - .op_type = GitOpType::INITIAL_COMMIT}; + GitOpKey op_key = { + .params = + { + repo_dir->GetPath(), // target_path + "", // git_hash + fmt::format("Content of tree {}", + key.hash), // message + content_dir->GetPath() // source_path + }, + .op_type = GitOpType::INITIAL_COMMIT}; critical_git_op_map->ConsumeAfterKeysReady( ts, {std::move(op_key)}, - [tmp_dir, // keep tmp_dir alive - out_dir, // keep stdout/stderr of command alive + [repo_dir, // keep repo_dir alive + content_dir, // keep content_dir alive + out_dir, // keep stdout/stderr of command alive critical_git_op_map, just_git_cas = op_result.git_cas, cmdline, @@ -492,7 +504,7 @@ auto CreateGitTreeFetchMap( if (not git_repo) { (*logger)( fmt::format("Could not open repository {}", - tmp_dir->GetPath().string()), + repo_dir->GetPath().string()), /*fatal=*/true); return; } @@ -541,14 +553,14 @@ auto CreateGitTreeFetchMap( /*fatal=*/true); return; } - auto target_path = tmp_dir->GetPath(); + auto target_path = repo_dir->GetPath(); // fetch all into Git cache auto just_git_repo = GitRepoRemote::Open(just_git_cas); if (not just_git_repo) { - (*logger)(fmt::format("Could not open Git " - "repository {}", - target_path.string()), - /*fatal=*/true); + (*logger)( + fmt::format("Could not open Git repository {}", + storage_config->GitRoot().string()), + /*fatal=*/true); return; } // define temp repo path @@ -634,21 +646,28 @@ auto CreateGitTreeFetchMap( // success (*setter)(false /*no cache hit*/); }, - [logger, commit = *op_result.result]( + [logger, + commit = *op_result.result, + target_path = storage_config->GitRoot()]( auto const& msg, bool fatal) { (*logger)( fmt::format("While running critical Git op " - "KEEP_TAG for commit {}:\n{}", + "KEEP_TAG for commit {} in " + "repository {}:\n{}", commit, + target_path.string(), msg), fatal); }); }, - [logger](auto const& msg, bool fatal) { - (*logger)(fmt::format("While running critical Git op " - "INITIAL_COMMIT:\n{}", - msg), - fatal); + [logger, target_path = repo_dir->GetPath()](auto const& msg, + bool fatal) { + (*logger)( + fmt::format("While running critical Git op " + "INITIAL_COMMIT for target {}:\n{}", + target_path.string(), + msg), + fatal); }); }, [logger, target_path = storage_config->GitRoot()](auto const& msg, diff --git a/src/other_tools/ops_maps/import_to_git_map.cpp b/src/other_tools/ops_maps/import_to_git_map.cpp index e883e0dd..02da0475 100644 --- a/src/other_tools/ops_maps/import_to_git_map.cpp +++ b/src/other_tools/ops_maps/import_to_git_map.cpp @@ -25,7 +25,6 @@ namespace { void KeepCommitAndSetTree( gsl::not_null<CriticalGitOpMap*> const& critical_git_op_map, std::string const& commit, - std::filesystem::path const& target_path, GitCASPtr const& just_git_cas, StorageConfig const& storage_config, gsl::not_null<TaskSystem*> const& ts, @@ -42,7 +41,7 @@ void KeepCommitAndSetTree( critical_git_op_map->ConsumeAfterKeysReady( ts, {std::move(op_key)}, - [commit, target_path, just_git_cas, setter, logger]( + [commit, just_git_cas, storage_config, setter, logger]( auto const& values) { GitOpValue op_result = *values[0]; // check flag @@ -54,7 +53,7 @@ void KeepCommitAndSetTree( auto just_git_repo = GitRepoRemote::Open(just_git_cas); if (not just_git_repo) { (*logger)(fmt::format("Could not open Git repository {}", - target_path.string()), + storage_config.GitRoot().string()), /*fatal=*/true); return; } @@ -75,7 +74,8 @@ void KeepCommitAndSetTree( (*setter)(std::pair<std::string, GitCASPtr>(*std::move(res), just_git_cas)); }, - [logger, commit, target_path](auto const& msg, bool fatal) { + [logger, commit, target_path = storage_config.GitRoot()]( + auto const& msg, bool fatal) { (*logger)(fmt::format("While running critical Git op KEEP_TAG for " "commit {} in target {}:\n{}", commit, @@ -101,11 +101,21 @@ auto CreateImportToGitMap( auto logger, auto /*unused*/, auto const& key) { - // Perform initial commit at import location: init + add . + commit + // The repository path that imports the content must be separate from + // the content path, to avoid polluting the entries + auto repo_dir = storage_config->CreateTypedTmpDir("import-repo"); + if (not repo_dir) { + (*logger)(fmt::format("Failed to create import repository tmp " + "directory for target {}", + key.target_path.string()), + true); + return; + } + // Commit content from target_path via the tmp repository GitOpKey op_key = {.params = { - key.target_path, // target_path - "", // git_hash + repo_dir->GetPath(), // target_path + "", // git_hash fmt::format("Content of {} {}", key.repo_type, key.content), // message @@ -115,8 +125,8 @@ auto CreateImportToGitMap( critical_git_op_map->ConsumeAfterKeysReady( ts, {std::move(op_key)}, - [critical_git_op_map, - target_path = key.target_path, + [repo_dir, // keep repo_dir alive + critical_git_op_map, git_bin, launcher, storage_config, @@ -145,9 +155,9 @@ auto CreateImportToGitMap( critical_git_op_map->ConsumeAfterKeysReady( ts, {std::move(op_key)}, - [critical_git_op_map, + [repo_dir, // keep repo_dir alive + critical_git_op_map, commit = *op_result.result, - target_path, git_bin, launcher, storage_config, @@ -172,6 +182,7 @@ auto CreateImportToGitMap( /*fatal=*/true); return; } + auto const& target_path = repo_dir->GetPath(); auto wrapped_logger = std::make_shared<AsyncMapConsumerLogger>( [logger, target_path](auto const& msg, @@ -198,16 +209,15 @@ auto CreateImportToGitMap( [logger, target_path](auto const& msg, bool fatal) { (*logger)( - fmt::format( - "While doing keep commit " - "and setting Git tree {}:\n{}", - target_path.string(), - msg), + fmt::format("While doing keep commit " + "and setting Git tree for " + "target {}:\n{}", + target_path.string(), + msg), fatal); }); KeepCommitAndSetTree(critical_git_op_map, commit, - target_path, op_result.git_cas, /*just_git_cas*/ *storage_config, ts, |