From c974b1eb4df53dc23bd80dd13ad514d8828dc986 Mon Sep 17 00:00:00 2001 From: Oliver Reiche Date: Mon, 1 Aug 2022 14:30:37 +0200 Subject: GitCAS: Implement reading git tree via libgit2 --- src/buildtool/file_system/git_cas.cpp | 136 +++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 2 deletions(-) (limited to 'src/buildtool/file_system/git_cas.cpp') diff --git a/src/buildtool/file_system/git_cas.cpp b/src/buildtool/file_system/git_cas.cpp index ccfef032..96674584 100644 --- a/src/buildtool/file_system/git_cas.cpp +++ b/src/buildtool/file_system/git_cas.cpp @@ -28,14 +28,14 @@ constexpr auto kOIDHexSize{GIT_OID_HEXSZ}; bool is_hex_id = false) noexcept -> std::optional { #ifndef BOOTSTRAP_BUILD_TOOL - if ((is_hex_id and id.size() < kOIDHexSize) or id.size() < kOIDRawSize) { + if (id.size() < (is_hex_id ? kOIDHexSize : kOIDRawSize)) { Logger::Log(LogLevel::Error, "invalid git object id {}", is_hex_id ? id : ToHexString(id)); return std::nullopt; } git_oid oid{}; - if (is_hex_id and git_oid_fromstr(&oid, id.data()) == 0) { + if (is_hex_id and git_oid_fromstr(&oid, id.c_str()) == 0) { return oid; } if (not is_hex_id and @@ -53,6 +53,44 @@ constexpr auto kOIDHexSize{GIT_OID_HEXSZ}; return std::nullopt; } +[[nodiscard]] auto ToHexString(git_oid const& oid) noexcept + -> std::optional { + std::string hex_id(GIT_OID_HEXSZ, '\0'); +#ifndef BOOTSTRAP_BUILD_TOOL + if (git_oid_fmt(hex_id.data(), &oid) != 0) { + return std::nullopt; + } +#endif + return hex_id; +} + +[[nodiscard]] auto ToRawString(git_oid const& oid) noexcept + -> std::optional { + if (auto hex_id = ToHexString(oid)) { + return FromHexString(*hex_id); + } + return std::nullopt; +} + +[[nodiscard]] auto GitFileModeToObjectType(git_filemode_t const& mode) noexcept + -> std::optional { + switch (mode) { + case GIT_FILEMODE_BLOB: + return ObjectType::File; + case GIT_FILEMODE_BLOB_EXECUTABLE: + return ObjectType::Executable; + case GIT_FILEMODE_TREE: + return ObjectType::Tree; + default: { + std::ostringstream str; + str << std::oct << static_cast(mode); + Logger::Log( + LogLevel::Error, "unsupported git filemode {}", str.str()); + return std::nullopt; + } + } +} + [[nodiscard]] auto GitTypeToObjectType(git_object_t const& type) noexcept -> std::optional { switch (type) { @@ -68,6 +106,51 @@ constexpr auto kOIDHexSize{GIT_OID_HEXSZ}; } } +[[maybe_unused]] [[nodiscard]] auto ValidateEntries( + GitCAS::tree_entries_t const& entries) -> bool { + return std::all_of(entries.begin(), entries.end(), [](auto entry) { + auto const& [id, nodes] = entry; + // for a given raw id, either all entries are trees or none of them + return std::all_of( + nodes.begin(), + nodes.end(), + [](auto entry) { return IsTreeObject(entry.type); }) or + std::none_of(nodes.begin(), nodes.end(), [](auto entry) { + return IsTreeObject(entry.type); + }); + }); +} + +auto const repo_closer = [](gsl::owner repo) { + if (repo != nullptr) { + git_repository_free(repo); + } +}; + +auto const tree_closer = [](gsl::owner tree) { + if (tree != nullptr) { + git_tree_free(tree); + } +}; + +[[nodiscard]] auto flat_tree_walker(const char* /*root*/, + const git_tree_entry* entry, + void* payload) noexcept -> int { + auto* entries = + reinterpret_cast(payload); // NOLINT + + std::string name = git_tree_entry_name(entry); + auto const* oid = git_tree_entry_id(entry); + if (auto raw_id = ToRawString(*oid)) { + if (auto type = + GitFileModeToObjectType(git_tree_entry_filemode(entry))) { + (*entries)[*raw_id].emplace_back(std::move(name), *type); + return 1; // return >=0 on success, 1 == skip subtrees (flat) + } + } + return -1; // fail +} + } // namespace auto GitCAS::Open(std::filesystem::path const& repo_path) noexcept @@ -137,6 +220,55 @@ auto GitCAS::ReadObject(std::string const& id, bool is_hex_id) const noexcept #endif } +auto GitCAS::ReadTree(std::string const& id, bool is_hex_id) const noexcept + -> std::optional { +#ifdef BOOTSTRAP_BUILD_TOOL + return std::nullopt; +#else + // create object id + auto oid = GitObjectID(id, is_hex_id); + if (not oid) { + return std::nullopt; + } + + // create fake repository from ODB + git_repository* repo_ptr{nullptr}; + if (git_repository_wrap_odb(&repo_ptr, odb_) != 0) { + Logger::Log(LogLevel::Debug, + "failed to create fake Git repository from object db"); + return std::nullopt; + } + auto fake_repo = std::unique_ptr{ + repo_ptr, repo_closer}; + + // lookup tree + git_tree* tree_ptr{nullptr}; + if (git_tree_lookup(&tree_ptr, fake_repo.get(), &(*oid)) != 0) { + Logger::Log(LogLevel::Debug, + "failed to lookup Git tree {}", + is_hex_id ? std::string{id} : ToHexString(id)); + return std::nullopt; + } + auto tree = + std::unique_ptr{tree_ptr, tree_closer}; + + // walk tree (flat) and create entries + tree_entries_t entries{}; + entries.reserve(git_tree_entrycount(tree.get())); + if (git_tree_walk( + tree.get(), GIT_TREEWALK_PRE, flat_tree_walker, &entries) != 0) { + Logger::Log(LogLevel::Debug, + "failed to walk Git tree {}", + is_hex_id ? std::string{id} : ToHexString(id)); + return std::nullopt; + } + + gsl_EnsuresAudit(ValidateEntries(entries)); + + return entries; +#endif +} + auto GitCAS::ReadHeader(std::string const& id, bool is_hex_id) const noexcept -> std::optional> { #ifndef BOOTSTRAP_BUILD_TOOL -- cgit v1.2.3