From 9e93d20d40ee1501b23c42b945dbf7e10420ac43 Mon Sep 17 00:00:00 2001 From: Paul Cristian Sarbu Date: Tue, 20 Aug 2024 17:46:20 +0200 Subject: GitRepo: Create commit from a directory explicitly... ...by writing its tree directly in the object database instead of working with the index. This allows the creation of trees that contain also entries with 'magic' names, such as the .git folder or .gitignore files. Callers must ensure the given directory only contains the needed entries. In particular, just-mr maps and serve service are updated to separate the import-to-Git repository path from the temporary path containing the content to be committed, to avoid polluting the content path with entries generated on repository initialization. --- src/buildtool/file_system/git_repo.cpp | 66 +++++++++------------------------- 1 file changed, 16 insertions(+), 50 deletions(-) (limited to 'src/buildtool/file_system/git_repo.cpp') 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( - 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( // NOLINT + raw_id->data())) != 0) { + (*logger)(fmt::format("subdir tree object id parsing in git " + "repository {} failed with:\n{}", GetGitPath().string(), GitLastError()), true /*fatal*/); -- cgit v1.2.3