diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/buildtool/file_system/TARGETS | 14 | ||||
-rw-r--r-- | src/buildtool/file_system/git_cas.cpp | 6 | ||||
-rw-r--r-- | src/buildtool/file_system/git_cas.hpp | 4 | ||||
-rw-r--r-- | src/buildtool/file_system/git_repo.cpp | 197 | ||||
-rw-r--r-- | src/buildtool/file_system/git_repo.hpp | 72 |
5 files changed, 293 insertions, 0 deletions
diff --git a/src/buildtool/file_system/TARGETS b/src/buildtool/file_system/TARGETS index 9d7a961b..46e5195b 100644 --- a/src/buildtool/file_system/TARGETS +++ b/src/buildtool/file_system/TARGETS @@ -64,6 +64,20 @@ , "stage": ["src", "buildtool", "file_system"] , "private-deps": [["src/buildtool/logging", "logging"], ["", "libgit2"]] } +, "git_repo": + { "type": ["@", "rules", "CC", "library"] + , "name": ["git_repo"] + , "hdrs": ["git_repo.hpp"] + , "srcs": ["git_repo.cpp"] + , "deps": + [ "git_cas" + , ["src/buildtool/multithreading", "async_map_consumer"] + , ["src/buildtool/logging", "logging"] + , ["", "libgit2"] + , ["src/utils/cpp", "path"] + ] + , "stage": ["src", "buildtool", "file_system"] + } , "file_root": { "type": ["@", "rules", "CC", "library"] , "name": ["file_root"] diff --git a/src/buildtool/file_system/git_cas.cpp b/src/buildtool/file_system/git_cas.cpp index 33fc665a..e03cece8 100644 --- a/src/buildtool/file_system/git_cas.cpp +++ b/src/buildtool/file_system/git_cas.cpp @@ -14,9 +14,11 @@ #include "src/buildtool/file_system/git_cas.hpp" +#include <cstring> #include <mutex> #include <sstream> +#include "gsl-lite/gsl-lite.hpp" #include "src/buildtool/logging/logger.hpp" #include "src/utils/cpp/hex_string.hpp" @@ -507,6 +509,10 @@ auto GitCAS::OpenODB(std::filesystem::path const& repo_path) noexcept -> bool { return false; } git_repository_odb(&odb_, repo); + // set root + git_path_ = std::filesystem::weakly_canonical(std::filesystem::absolute( + std::filesystem::path(git_repository_path(repo)))); + // release resources git_repository_free(repo); } if (odb_ == nullptr) { diff --git a/src/buildtool/file_system/git_cas.hpp b/src/buildtool/file_system/git_cas.hpp index 3670d188..47592927 100644 --- a/src/buildtool/file_system/git_cas.hpp +++ b/src/buildtool/file_system/git_cas.hpp @@ -127,9 +127,13 @@ class GitCAS { // IMPORTANT: the GitContext needs to be initialized before any git object! GitContext git_context_{}; // maintains a Git context while CAS is alive git_odb* odb_{nullptr}; + // git folder path of repo; used for logging + std::filesystem::path git_path_{}; [[nodiscard]] auto OpenODB(std::filesystem::path const& repo_path) noexcept -> bool; + + friend class GitRepo; // allow access to ODB }; #endif // INCLUDED_SRC_BUILDTOOL_FILE_SYSTEM_GIT_CAS_HPP diff --git a/src/buildtool/file_system/git_repo.cpp b/src/buildtool/file_system/git_repo.cpp new file mode 100644 index 00000000..4b46bd9d --- /dev/null +++ b/src/buildtool/file_system/git_repo.cpp @@ -0,0 +1,197 @@ +// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <src/buildtool/file_system/git_repo.hpp> + +#include "src/buildtool/logging/logger.hpp" +#include "src/utils/cpp/path.hpp" + +extern "C" { +#include <git2.h> +} + +namespace { + +constexpr std::size_t kWaitTime{2}; // time in ms between tries for git locks + +[[nodiscard]] auto GitLastError() noexcept -> std::string { + git_error const* err{nullptr}; + if ((err = git_error_last()) != nullptr and err->message != nullptr) { + return fmt::format("error code {}: {}", err->klass, err->message); + } + return "<unknown error>"; +} + +} // namespace + +auto GitRepo::Open(GitCASPtr git_cas) noexcept -> std::optional<GitRepo> { +#ifdef BOOTSTRAP_BUILD_TOOL + return std::nullopt; +#else + auto repo = GitRepo(std::move(git_cas)); + if (repo.repo_ == nullptr) { + return std::nullopt; + } + return repo; +#endif // BOOTSTRAP_BUILD_TOOL +} + +auto GitRepo::Open(std::filesystem::path const& repo_path) noexcept + -> std::optional<GitRepo> { +#ifdef BOOTSTRAP_BUILD_TOOL + return std::nullopt; +#else + auto repo = GitRepo(repo_path); + if (repo.repo_ == nullptr) { + return std::nullopt; + } + return repo; +#endif // BOOTSTRAP_BUILD_TOOL +} + +GitRepo::GitRepo(GitCASPtr git_cas) noexcept { +#ifndef BOOTSTRAP_BUILD_TOOL + if (git_cas != nullptr) { + if (git_repository_wrap_odb(&repo_, git_cas->odb_) != 0) { + Logger::Log(LogLevel::Error, + "could not create wrapper for git repository"); + git_repository_free(repo_); + repo_ = nullptr; + return; + } + is_repo_fake_ = true; + git_cas_ = std::move(git_cas); + } + else { + Logger::Log(LogLevel::Error, + "git repository creation attempted with null odb!"); + } +#endif // BOOTSTRAP_BUILD_TOOL +} + +GitRepo::GitRepo(std::filesystem::path const& repo_path) noexcept { +#ifndef BOOTSTRAP_BUILD_TOOL + try { + static std::mutex repo_mutex{}; + std::unique_lock lock{repo_mutex}; + auto cas = std::make_shared<GitCAS>(); + // open repo, but retain it + if (git_repository_open(&repo_, repo_path.c_str()) != 0) { + Logger::Log(LogLevel::Error, + "opening git repository {} failed with:\n{}", + repo_path.string(), + GitLastError()); + git_repository_free(repo_); + repo_ = nullptr; + return; + } + // get odb + git_repository_odb(&cas->odb_, repo_); + if (cas->odb_ == nullptr) { + Logger::Log(LogLevel::Error, + "retrieving odb of git repository {} failed with:\n{}", + repo_path.string(), + GitLastError()); + git_repository_free(repo_); + repo_ = nullptr; + return; + } + is_repo_fake_ = false; + // save root path + cas->git_path_ = ToNormalPath(std::filesystem::absolute( + std::filesystem::path(git_repository_path(repo_)))); + // retain the pointer + git_cas_ = std::static_pointer_cast<GitCAS const>(cas); + } catch (std::exception const& ex) { + Logger::Log(LogLevel::Error, + "opening git object database failed with:\n{}", + ex.what()); + repo_ = nullptr; + } +#endif // BOOTSTRAP_BUILD_TOOL +} + +GitRepo::GitRepo(GitRepo&& other) noexcept + : git_cas_{std::move(other.git_cas_)}, + repo_{other.repo_}, + is_repo_fake_{other.is_repo_fake_} { + other.repo_ = nullptr; +} + +auto GitRepo::operator=(GitRepo&& other) noexcept -> GitRepo& { + git_cas_ = std::move(other.git_cas_); + repo_ = other.repo_; + is_repo_fake_ = other.is_repo_fake_; + other.git_cas_ = nullptr; + return *this; +} + +auto GitRepo::InitAndOpen(std::filesystem::path const& repo_path, + bool is_bare) noexcept -> std::optional<GitRepo> { +#ifndef BOOTSTRAP_BUILD_TOOL + try { + static std::mutex repo_mutex{}; + std::unique_lock lock{repo_mutex}; + + auto git_state = GitContext(); // initialize libgit2 + + git_repository* tmp_repo{nullptr}; + size_t max_attempts = 3; // number of tries + int err = 0; + while (max_attempts > 0) { + --max_attempts; + err = git_repository_init( + &tmp_repo, repo_path.c_str(), static_cast<size_t>(is_bare)); + if (err == 0) { + git_repository_free(tmp_repo); + return GitRepo(repo_path); // success + } + git_repository_free(tmp_repo); // cleanup before next attempt + // check if init hasn't already happened in another process + if (git_repository_open_ext(nullptr, + repo_path.c_str(), + GIT_REPOSITORY_OPEN_NO_SEARCH, + nullptr) == 0) { + return GitRepo(repo_path); // success + } + // repo still not created, so sleep and try again + std::this_thread::sleep_for(std::chrono::milliseconds(kWaitTime)); + } + Logger::Log( + LogLevel::Error, + "initializing git repository {} failed with error code:\n{}", + (repo_path / "").string(), + err); + } catch (std::exception const& ex) { + Logger::Log(LogLevel::Error, + "initializing git repository {} failed with:\n{}", + (repo_path / "").string(), + ex.what()); + } +#endif // BOOTSTRAP_BUILD_TOOL + return std::nullopt; +} + +auto GitRepo::GetGitCAS() const noexcept -> GitCASPtr { + return git_cas_; +} + +GitRepo::~GitRepo() noexcept { + // release resources + git_repository_free(repo_); +} + +auto GitRepo::IsRepoFake() const noexcept -> bool { + return is_repo_fake_; +} diff --git a/src/buildtool/file_system/git_repo.hpp b/src/buildtool/file_system/git_repo.hpp new file mode 100644 index 00000000..ae0c183b --- /dev/null +++ b/src/buildtool/file_system/git_repo.hpp @@ -0,0 +1,72 @@ +// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef INCLUDED_SRC_BUILDTOOL_FILE_SYSTEM_GIT_REPO_HPP +#define INCLUDED_SRC_BUILDTOOL_FILE_SYSTEM_GIT_REPO_HPP + +#include "src/buildtool/file_system/git_cas.hpp" +#include "src/buildtool/multithreading/async_map_consumer.hpp" + +extern "C" { +using git_repository = struct git_repository; +} + +/// \brief Git repository logic. +/// Models both a real repository, owning the underlying ODB +/// (non-thread-safe), as well as a "fake" repository, which only wraps an +/// existing ODB, allowing thread-safe operations. +class GitRepo { + public: + GitRepo() = delete; // no default ctor + + // allow only move, no copy + GitRepo(GitRepo const&) = delete; + GitRepo(GitRepo&&) noexcept; + auto operator=(GitRepo const&) = delete; + auto operator=(GitRepo&& other) noexcept -> GitRepo&; + + /// \brief Factory to wrap existing open CAS in a "fake" repository. + [[nodiscard]] static auto Open(GitCASPtr git_cas) noexcept + -> std::optional<GitRepo>; + + /// \brief Factory to open existing real repository at given location. + [[nodiscard]] static auto Open( + std::filesystem::path const& repo_path) noexcept + -> std::optional<GitRepo>; + + /// \brief Factory to initialize and open new real repository at location. + /// Returns nullopt if repository init fails even after repeated tries. + [[nodiscard]] static auto InitAndOpen( + std::filesystem::path const& repo_path, + bool is_bare) noexcept -> std::optional<GitRepo>; + + [[nodiscard]] auto GetGitCAS() const noexcept -> GitCASPtr; + + [[nodiscard]] auto IsRepoFake() const noexcept -> bool; + + ~GitRepo() noexcept; + + private: + GitCASPtr git_cas_{nullptr}; + git_repository* repo_{nullptr}; + // default to real repo, as that is non-thread-safe + bool is_repo_fake_{false}; + + /// \brief Open "fake" repository wrapper for existing CAS. + explicit GitRepo(GitCASPtr git_cas) noexcept; + /// \brief Open real repository at given location. + explicit GitRepo(std::filesystem::path const& repo_path) noexcept; +}; + +#endif // INCLUDED_SRC_BUILDTOOL_FILE_SYSTEM_GIT_REPO_HPP
\ No newline at end of file |