summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Cristian Sarbu <paul.cristian.sarbu@huawei.com>2024-04-04 16:36:48 +0200
committerPaul Cristian Sarbu <paul.cristian.sarbu@huawei.com>2024-04-10 15:25:14 +0200
commitd3ec6b7294d44e1cd524ac5bbb9048d415950e99 (patch)
tree373735cc6edf9f78312c352aa3030742a42e22ac
parent4fe5fc8aec6e391a5e300e234bdc41375bff1d9e (diff)
downloadjustbuild-d3ec6b7294d44e1cd524ac5bbb9048d415950e99.tar.gz
GitRepo: Add method to keep tree alive by tagging
Also adds an appropriate test for this method.
-rw-r--r--src/buildtool/file_system/git_repo.cpp123
-rw-r--r--src/buildtool/file_system/git_repo.hpp9
-rw-r--r--test/buildtool/file_system/TARGETS1
-rw-r--r--test/buildtool/file_system/git_repo.test.cpp30
4 files changed, 163 insertions, 0 deletions
diff --git a/src/buildtool/file_system/git_repo.cpp b/src/buildtool/file_system/git_repo.cpp
index c7ecddda..4fefc76b 100644
--- a/src/buildtool/file_system/git_repo.cpp
+++ b/src/buildtool/file_system/git_repo.cpp
@@ -934,6 +934,129 @@ auto GitRepo::FetchFromPath(std::shared_ptr<git_config> cfg,
#endif // BOOTSTRAP_BUILD_TOOL
}
+auto GitRepo::KeepTree(std::string const& tree_id,
+ std::string const& message,
+ anon_logger_ptr const& logger) noexcept -> bool {
+#ifdef BOOTSTRAP_BUILD_TOOL
+ return false;
+#else
+ try {
+ // only possible for real repository!
+ if (IsRepoFake()) {
+ (*logger)("cannot commit and tag a tree using a fake repository!",
+ true /*fatal*/);
+ return false;
+ }
+ // share the odb lock
+ std::shared_lock lock{GetGitCAS()->mutex_};
+
+ // get tree oid
+ git_oid tree_oid;
+ if (git_oid_fromstr(&tree_oid, tree_id.c_str()) != 0) {
+ (*logger)(fmt::format("tree ID parsing in git repository {} failed "
+ "with:\n{}",
+ GetGitCAS()->git_path_.string(),
+ GitLastError()),
+ true /*fatal*/);
+ return false;
+ }
+ // get tree object from oid
+ git_object* target_ptr{nullptr};
+ if (git_object_lookup(
+ &target_ptr, repo_->Ptr(), &tree_oid, GIT_OBJECT_TREE) != 0) {
+ (*logger)(fmt::format("object lookup for tree {} in repository "
+ "{} failed with:\n{}",
+ tree_id,
+ GetGitCAS()->git_path_.string(),
+ GitLastError()),
+ true /*fatal*/);
+ git_object_free(target_ptr);
+ return false;
+ }
+ auto target = std::unique_ptr<git_object, decltype(&object_closer)>(
+ target_ptr, object_closer);
+
+ // set signature
+ git_signature* signature_ptr{nullptr};
+ if (git_signature_new(
+ &signature_ptr, "Nobody", "nobody@example.org", 0, 0) != 0) {
+ (*logger)(
+ fmt::format("creating signature in git repository {} failed "
+ "with:\n{}",
+ GetGitCAS()->git_path_.string(),
+ GitLastError()),
+ true /*fatal*/);
+ // cleanup resources
+ git_signature_free(signature_ptr);
+ return false;
+ }
+ auto signature =
+ std::unique_ptr<git_signature, decltype(&signature_closer)>(
+ signature_ptr, signature_closer);
+
+ // create tag
+ git_oid oid;
+ auto name = fmt::format("keep-{}", tree_id);
+ git_strarray tag_names{};
+
+ // check if tag hasn't already been added by another process
+ if (git_tag_list_match(&tag_names, name.c_str(), repo_->Ptr()) == 0 and
+ tag_names.count > 0) {
+ git_strarray_dispose(&tag_names);
+ return true; // success!
+ }
+ git_strarray_dispose(&tag_names); // free any allocated unused space
+
+ size_t max_attempts = kGitLockNumTries; // number of tries
+ int err = 0;
+ std::string err_mess{};
+ while (max_attempts > 0) {
+ --max_attempts;
+ err = git_tag_create(&oid,
+ repo_->Ptr(),
+ name.c_str(),
+ target.get(), /*tree*/
+ signature.get(), /*tagger*/
+ message.c_str(),
+ 1 /*force*/);
+ if (err == 0) {
+ return true; // success!
+ }
+ err_mess = GitLastError(); // store last error message
+ // only retry if failure is due to locking
+ if (err != GIT_ELOCKED) {
+ break;
+ }
+ // check if tag hasn't already been added by another process
+ if (git_tag_list_match(&tag_names, name.c_str(), repo_->Ptr()) ==
+ 0 and
+ tag_names.count > 0) {
+ git_strarray_dispose(&tag_names);
+ return true; // success!
+ }
+ git_strarray_dispose(
+ &tag_names); // free any allocated unused space
+ // tag still not in, so sleep and try again
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(kGitLockWaitTime));
+ }
+ (*logger)(fmt::format("tag creation for tree {} in git repository {} "
+ "failed with:\n{}",
+ tree_id,
+ GetGitCAS()->git_path_.string(),
+ err_mess),
+ true /*fatal*/);
+ return false;
+ } catch (std::exception const& ex) {
+ Logger::Log(LogLevel::Error,
+ "keep tree {} failed with:\n{}",
+ tree_id,
+ ex.what());
+ return false;
+ }
+#endif // BOOTSTRAP_BUILD_TOOL
+}
+
auto GitRepo::GetSubtreeFromCommit(std::string const& commit,
std::string const& subdir,
anon_logger_ptr const& logger) noexcept
diff --git a/src/buildtool/file_system/git_repo.hpp b/src/buildtool/file_system/git_repo.hpp
index 60353a00..54706e41 100644
--- a/src/buildtool/file_system/git_repo.hpp
+++ b/src/buildtool/file_system/git_repo.hpp
@@ -183,6 +183,15 @@ class GitRepo {
anon_logger_ptr const& logger) noexcept
-> bool;
+ /// \brief Ensure given tree is kept alive via a tag. It is expected that
+ /// the tree is part of the repository already.
+ /// Only possible with real repository and thus non-thread-safe.
+ /// Returns success flag.
+ /// It guarantees the logger is called exactly once with fatal if failure.
+ [[nodiscard]] auto KeepTree(std::string const& tree_id,
+ std::string const& message,
+ anon_logger_ptr const& logger) noexcept -> bool;
+
/// \brief Get the tree id of a subtree given the root commit
/// Calling it from a fake repository allows thread-safe use.
/// Returns an error + data union, where at index 0 is a flag stating the
diff --git a/test/buildtool/file_system/TARGETS b/test/buildtool/file_system/TARGETS
index 8121b39a..e63bb06d 100644
--- a/test/buildtool/file_system/TARGETS
+++ b/test/buildtool/file_system/TARGETS
@@ -123,6 +123,7 @@
, ["@", "src", "src/buildtool/logging", "log_level"]
, ["@", "src", "src/buildtool/logging", "logging"]
, ["@", "src", "src/utils/cpp", "atomic"]
+ , ["@", "src", "src/utils/cpp", "hex_string"]
, ["utils", "shell_quoting"]
]
, "stage": ["test", "buildtool", "file_system"]
diff --git a/test/buildtool/file_system/git_repo.test.cpp b/test/buildtool/file_system/git_repo.test.cpp
index a6edfc8a..67ad19cd 100644
--- a/test/buildtool/file_system/git_repo.test.cpp
+++ b/test/buildtool/file_system/git_repo.test.cpp
@@ -31,6 +31,7 @@
#include "src/buildtool/logging/log_level.hpp"
#include "src/buildtool/logging/logger.hpp"
#include "src/utils/cpp/atomic.hpp"
+#include "src/utils/cpp/hex_string.hpp"
#include "test/utils/shell_quoting.hpp"
namespace {
@@ -44,6 +45,9 @@ auto const kBazId = std::string{"1868f82682c290f0b1db3cacd092727eef1fa57f"};
auto const kFooId = std::string{"19102815663d23f8b75a47e7a01965dcdc96468c"};
auto const kBarId = std::string{"ba0e162e1c47469e3fe4b393a8bf8c569f302116"};
+auto const kFooBarTreeId =
+ std::string{"27b32561185c2825150893774953906c6daa6798"};
+
} // namespace
class TestUtils {
@@ -240,6 +244,32 @@ TEST_CASE("Single-threaded real repository local operations", "[git_repo]") {
CHECK(repo_fetch_branch->FetchFromPath(
nullptr, *path_fetch_branch, "master", logger));
}
+
+ SECTION("Tag tree") {
+ auto repo_tag_path = TestUtils::CreateTestRepo(true);
+ REQUIRE(repo_tag_path);
+ auto repo_tag = GitRepo::Open(*repo_tag_path);
+ REQUIRE(repo_tag);
+ CHECK_FALSE(repo_tag->IsRepoFake());
+
+ // tag tree already root of a commit
+ CHECK(repo_tag->KeepTree(kRootId, "test tag 1", logger));
+
+ // tag tree part of another commit
+ CHECK(repo_tag->KeepTree(kBazId, "test tag 2", logger));
+
+ // tag uncommitted tree
+ auto foo_bar = GitRepo::tree_entries_t{
+ {FromHexString(kFooId).value_or<std::string>({}),
+ {GitRepo::tree_entry_t{"foo", ObjectType::File}}},
+ {FromHexString(kBarId).value_or<std::string>({}),
+ {GitRepo::tree_entry_t{"bar", ObjectType::Executable}}}};
+ auto foo_bar_id = repo_tag->CreateTree(foo_bar);
+ REQUIRE(foo_bar_id);
+ auto tree_id = ToHexString(*foo_bar_id);
+ CHECK(tree_id == kFooBarTreeId);
+ CHECK(repo_tag->KeepTree(tree_id, "test tag 3", logger));
+ }
}
// NOTE: "fake" repo ops tests split into two batches as workaround for