summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/buildtool/file_system/git_repo.cpp117
1 files changed, 81 insertions, 36 deletions
diff --git a/src/buildtool/file_system/git_repo.cpp b/src/buildtool/file_system/git_repo.cpp
index 5cdb552b..d378a2ba 100644
--- a/src/buildtool/file_system/git_repo.cpp
+++ b/src/buildtool/file_system/git_repo.cpp
@@ -16,6 +16,9 @@
#include <algorithm>
#include <cstddef>
+#include <map>
+#include <sstream>
+#include <string_view>
#include <thread>
#include <unordered_set>
@@ -115,11 +118,32 @@ std::unordered_set<git_filemode_t> const kNonSpecialGitFileModes{
}
}
+[[nodiscard]] auto ObjectTypeToPerm(ObjectType type) noexcept -> std::string {
+ switch (type) {
+ case ObjectType::File:
+ return "100644";
+ case ObjectType::Executable:
+ return "100755";
+ case ObjectType::Tree:
+ return "40000";
+ case ObjectType::Symlink:
+ return "120000";
+ }
+ return ""; // make gcc happy
+}
+
#ifndef NDEBUG
-[[nodiscard]] auto ValidateEntries(GitRepo::tree_entries_t const& entries)
- -> bool {
- return std::all_of(entries.begin(), entries.end(), [](auto entry) {
+/// \brief Debug-level check that given tree entries are consistent. If needed,
+/// also check that the entries are in the underlying object database of the
+/// provided CAS instance.
+[[nodiscard]] auto ValidateEntries(GitRepo::tree_entries_t const& entries,
+ GitCASPtr const& cas = nullptr) -> bool {
+ return std::all_of(entries.begin(), entries.end(), [cas](auto entry) {
auto const& [id, nodes] = entry;
+ // if CAS given, check that the entry is in the object database
+ if (cas != nullptr and not cas->ReadHeader(id)) {
+ return false;
+ }
// for a given raw id, either all entries are trees or none of them
return std::all_of(
nodes.begin(),
@@ -1861,6 +1885,7 @@ auto GitRepo::ReadTree(std::string const& id,
}
#ifndef NDEBUG
+ // Check consistency of entries. No need to check if entries exist.
EnsuresAudit(ValidateEntries(entries));
#endif
@@ -1880,48 +1905,68 @@ auto GitRepo::CreateTree(tree_entries_t const& entries) const noexcept
return std::nullopt;
#else
#ifndef NDEBUG
- ExpectsAudit(ValidateEntries(entries));
+ // Check consistency of entries. Also check that entries exist.
+ ExpectsAudit(ValidateEntries(entries, GetGitCAS()));
#endif // NDEBUG
// share the odb lock
std::shared_lock lock{GetGitCAS()->mutex_};
- git_treebuilder* builder_ptr{nullptr};
- if (git_treebuilder_new(&builder_ptr, repo_->Ptr(), nullptr) != 0) {
- Logger::Log(LogLevel::Debug, "failed to create Git tree builder");
- return std::nullopt;
- }
- auto builder =
- std::unique_ptr<git_treebuilder, decltype(&treebuilder_closer)>{
- builder_ptr, treebuilder_closer};
-
- for (auto const& [raw_id, es] : entries) {
- auto id = GitObjectID(raw_id, /*is_hex_id=*/false);
- for (auto const& entry : es) {
- if (not id or git_treebuilder_insert(
- nullptr,
- builder.get(),
- entry.name.c_str(),
- &(*id),
- ObjectTypeToGitFileMode(entry.type)) != 0) {
- Logger::Log(
- LogLevel::Debug,
- "failed adding object {} to Git tree{}",
- ToHexString(raw_id),
- id ? fmt::format(" with:\n{}", GitLastError()) : "");
- return std::nullopt;
+ try {
+ // As the libgit2 treebuilder checks for magic names and does not allow
+ // us to add any and all entries to a Git tree, we resort to
+ // constructing the tree content ourselves and add it manually to the
+ // repository ODB.
+
+ // We need to sort the filenames according to Git rules: tree entries
+ // need to be considered "as if" their filename has a trailing
+ // separator ('/').
+ std::map<std::string, std::pair<std::string, ObjectType>> sorted;
+ for (auto const& [raw_id, es] : entries) {
+ for (auto const& entry : es) {
+ sorted.emplace(
+ entry.name + (IsTreeObject(entry.type) ? "/" : ""),
+ std::make_pair(raw_id, entry.type));
}
}
- }
- git_oid oid;
- if (git_treebuilder_write(&oid, builder.get()) != 0) {
- return std::nullopt;
- }
- auto raw_id = ToRawString(oid);
- if (not raw_id) {
+ // Compute the tree content. For tree entries the trailing slash needs
+ // to be removed from filename before appending it.
+ std::stringstream tree_content{};
+ for (auto const& [name, entry] : sorted) {
+ std::string_view const filename{
+ name.data(),
+ name.size() -
+ static_cast<std::size_t>(IsTreeObject(entry.second))};
+ // tree format: "<perm> <filename>\0<binary_hash>[next entries...]"
+ tree_content << fmt::format("{} {}",
+ ObjectTypeToPerm(entry.second),
+ filename)
+ << '\0' << entry.first;
+ }
+
+ // Write tree to ODB and return raw id string
+ git_oid oid;
+ auto const tree_content_str = tree_content.str();
+ if (git_odb_write(&oid,
+ GetGitOdb().get(),
+ tree_content_str.c_str(),
+ tree_content_str.size(),
+ GIT_OBJECT_TREE) != 0) {
+ Logger::Log(LogLevel::Debug,
+ "failed writing tree to ODB with:\n{}",
+ GitLastError());
+ return std::nullopt;
+ }
+ auto raw_id = ToRawString(oid);
+ if (not raw_id) {
+ return std::nullopt;
+ }
+ return std::move(*raw_id);
+ } catch (std::exception const& ex) {
+ Logger::Log(
+ LogLevel::Error, "creating tree failed with:\n{}", ex.what());
return std::nullopt;
}
- return std::move(*raw_id);
#endif
}