From 4f7ffc500be6be77bed08ffaceefbb58660ecbc2 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 21 Jul 2025 15:33:30 +0200 Subject: [PATCH 1/4] commit_wrapper now built from reposiroty_wrapper --- src/wrapper/commit_wrapper.cpp | 12 +++++++----- src/wrapper/commit_wrapper.hpp | 13 +++++-------- src/wrapper/repository_wrapper.cpp | 17 ++++++++++++++++- src/wrapper/repository_wrapper.hpp | 5 +++++ src/wrapper/wrapper_base.hpp | 1 + 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/wrapper/commit_wrapper.cpp b/src/wrapper/commit_wrapper.cpp index e8168e0..02dee81 100644 --- a/src/wrapper/commit_wrapper.cpp +++ b/src/wrapper/commit_wrapper.cpp @@ -1,6 +1,9 @@ -#include "../utils/git_exception.hpp" #include "../wrapper/commit_wrapper.hpp" -#include "../wrapper/repository_wrapper.hpp" + +commit_wrapper::commit_wrapper(git_commit* commit) + : base_type(commit) +{ +} commit_wrapper::~commit_wrapper() { @@ -8,8 +11,7 @@ commit_wrapper::~commit_wrapper() p_resource = nullptr; } - -commit_wrapper commit_wrapper::from_reference_name(const repository_wrapper& repo, const std::string& ref_name) +/*commit_wrapper commit_wrapper::from_reference_name(const repository_wrapper& repo, const std::string& ref_name) { git_oid oid_parent_commit; throwIfError(git_reference_name_to_id(&oid_parent_commit, repo, ref_name.c_str())); @@ -17,4 +19,4 @@ commit_wrapper commit_wrapper::from_reference_name(const repository_wrapper& rep commit_wrapper cw; throwIfError(git_commit_lookup(&(cw.p_resource), repo, &oid_parent_commit)); return cw; -} +}*/ diff --git a/src/wrapper/commit_wrapper.hpp b/src/wrapper/commit_wrapper.hpp index 379bd44..4c27aaf 100644 --- a/src/wrapper/commit_wrapper.hpp +++ b/src/wrapper/commit_wrapper.hpp @@ -1,26 +1,23 @@ #pragma once -#include - #include #include "../wrapper/wrapper_base.hpp" -class repository_wrapper; - class commit_wrapper : public wrapper_base { public: + using base_type = wrapper_base; + ~commit_wrapper(); commit_wrapper(commit_wrapper&&) noexcept = default; commit_wrapper& operator=(commit_wrapper&&) noexcept = default; - static commit_wrapper - from_reference_name(const repository_wrapper& repo, const std::string& ref_name = "HEAD"); - private: - commit_wrapper() = default; + commit_wrapper(git_commit* commit); + + friend class repository_wrapper; }; diff --git a/src/wrapper/repository_wrapper.cpp b/src/wrapper/repository_wrapper.cpp index c35eece..65e06f7 100644 --- a/src/wrapper/repository_wrapper.cpp +++ b/src/wrapper/repository_wrapper.cpp @@ -36,7 +36,7 @@ index_wrapper repository_wrapper::make_index() branch_wrapper repository_wrapper::create_branch(const std::string& name, bool force) { - return create_branch(name, commit_wrapper::from_reference_name(*this), force); + return create_branch(name, find_commit(), force); } branch_wrapper repository_wrapper::create_branch(const std::string& name, const commit_wrapper& commit, bool force) @@ -59,3 +59,18 @@ branch_iterator repository_wrapper::iterate_branches(git_branch_t type) const throwIfError(git_branch_iterator_new(&iter, *this, type)); return branch_iterator(iter); } + + +commit_wrapper repository_wrapper::find_commit(const std::string& ref_name) const +{ + git_oid oid_parent_commit; + throwIfError(git_reference_name_to_id(&oid_parent_commit, *this, ref_name.c_str())); + return find_commit(oid_parent_commit); +} + +commit_wrapper repository_wrapper::find_commit(const git_oid& id) const +{ + git_commit* commit; + throwIfError(git_commit_lookup(&commit, *this, &id)); + return commit_wrapper(commit); +} diff --git a/src/wrapper/repository_wrapper.hpp b/src/wrapper/repository_wrapper.hpp index cf07a20..5af5594 100644 --- a/src/wrapper/repository_wrapper.hpp +++ b/src/wrapper/repository_wrapper.hpp @@ -33,6 +33,11 @@ class repository_wrapper : public wrapper_base branch_iterator iterate_branches(git_branch_t type) const; + // Commits + + commit_wrapper find_commit(const std::string& ref_name = "HEAD") const; + commit_wrapper find_commit(const git_oid& id) const; + private: repository_wrapper() = default; diff --git a/src/wrapper/wrapper_base.hpp b/src/wrapper/wrapper_base.hpp index 424f274..08a5662 100644 --- a/src/wrapper/wrapper_base.hpp +++ b/src/wrapper/wrapper_base.hpp @@ -1,5 +1,6 @@ #pragma once +#include template class wrapper_base From 854f9267e3392e11ff5be29d81bb91159b182d1f Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 21 Jul 2025 15:54:11 +0200 Subject: [PATCH 2/4] Replaced std::string with std::string_view --- src/wrapper/repository_wrapper.cpp | 22 +++++++++++----------- src/wrapper/repository_wrapper.hpp | 14 +++++++------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/wrapper/repository_wrapper.cpp b/src/wrapper/repository_wrapper.cpp index 65e06f7..7cbac9f 100644 --- a/src/wrapper/repository_wrapper.cpp +++ b/src/wrapper/repository_wrapper.cpp @@ -7,17 +7,17 @@ repository_wrapper::~repository_wrapper() p_resource=nullptr; } -repository_wrapper repository_wrapper::open(const std::string& directory) +repository_wrapper repository_wrapper::open(std::string_view directory) { repository_wrapper rw; - throwIfError(git_repository_open(&(rw.p_resource), directory.c_str())); + throwIfError(git_repository_open(&(rw.p_resource), directory.data())); return rw; } -repository_wrapper repository_wrapper::init(const std::string& directory, bool bare) +repository_wrapper repository_wrapper::init(std::string_view directory, bool bare) { repository_wrapper rw; - throwIfError(git_repository_init(&(rw.p_resource), directory.c_str(), bare)); + throwIfError(git_repository_init(&(rw.p_resource), directory.data(), bare)); return rw; } @@ -34,22 +34,22 @@ index_wrapper repository_wrapper::make_index() return index; } -branch_wrapper repository_wrapper::create_branch(const std::string& name, bool force) +branch_wrapper repository_wrapper::create_branch(std::string_view name, bool force) { return create_branch(name, find_commit(), force); } -branch_wrapper repository_wrapper::create_branch(const std::string& name, const commit_wrapper& commit, bool force) +branch_wrapper repository_wrapper::create_branch(std::string_view name, const commit_wrapper& commit, bool force) { git_reference* branch = nullptr; - throwIfError(git_branch_create(&branch, *this, name.c_str(), commit, force)); + throwIfError(git_branch_create(&branch, *this, name.data(), commit, force)); return branch_wrapper(branch); } -branch_wrapper repository_wrapper::find_branch(const std::string& name) +branch_wrapper repository_wrapper::find_branch(std::string_view name) { git_reference* branch = nullptr; - throwIfError(git_branch_lookup(&branch, *this, name.c_str(), GIT_BRANCH_LOCAL)); + throwIfError(git_branch_lookup(&branch, *this, name.data(), GIT_BRANCH_LOCAL)); return branch_wrapper(branch); } @@ -61,10 +61,10 @@ branch_iterator repository_wrapper::iterate_branches(git_branch_t type) const } -commit_wrapper repository_wrapper::find_commit(const std::string& ref_name) const +commit_wrapper repository_wrapper::find_commit(std::string_view ref_name) const { git_oid oid_parent_commit; - throwIfError(git_reference_name_to_id(&oid_parent_commit, *this, ref_name.c_str())); + throwIfError(git_reference_name_to_id(&oid_parent_commit, *this, ref_name.data())); return find_commit(oid_parent_commit); } diff --git a/src/wrapper/repository_wrapper.hpp b/src/wrapper/repository_wrapper.hpp index 5af5594..9e3e03c 100644 --- a/src/wrapper/repository_wrapper.hpp +++ b/src/wrapper/repository_wrapper.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include @@ -19,23 +19,23 @@ class repository_wrapper : public wrapper_base repository_wrapper(repository_wrapper&&) noexcept = default; repository_wrapper& operator=(repository_wrapper&&) noexcept = default; - static repository_wrapper init(const std::string& directory, bool bare); - static repository_wrapper open(const std::string& directory); + static repository_wrapper init(std::string_view directory, bool bare); + static repository_wrapper open(std::string_view directory); reference_wrapper head() const; index_wrapper make_index(); - branch_wrapper create_branch(const std::string& name, bool force); - branch_wrapper create_branch(const std::string& name, const commit_wrapper& commit, bool force); + branch_wrapper create_branch(std::string_view name, bool force); + branch_wrapper create_branch(std::string_view name, const commit_wrapper& commit, bool force); - branch_wrapper find_branch(const std::string& name); + branch_wrapper find_branch(std::string_view name); branch_iterator iterate_branches(git_branch_t type) const; // Commits - commit_wrapper find_commit(const std::string& ref_name = "HEAD") const; + commit_wrapper find_commit(std::string_view ref_name = "HEAD") const; commit_wrapper find_commit(const git_oid& id) const; private: From 1c88f5f88480d49f08e53623e31fcae1bd5ecca9 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 21 Jul 2025 16:11:13 +0200 Subject: [PATCH 3/4] Annotated commit wrapper --- CMakeLists.txt | 2 ++ src/wrapper/annotated_commit_wrapper.cpp | 23 +++++++++++++++++++ src/wrapper/annotated_commit_wrapper.hpp | 29 ++++++++++++++++++++++++ src/wrapper/commit_wrapper.cpp | 10 +++----- src/wrapper/commit_wrapper.hpp | 2 ++ src/wrapper/repository_wrapper.cpp | 16 ++++++++++++- src/wrapper/repository_wrapper.hpp | 13 +++++++---- 7 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 src/wrapper/annotated_commit_wrapper.cpp create mode 100644 src/wrapper/annotated_commit_wrapper.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 49f3295..a4e8f56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,8 @@ set(GIT2CPP_SRC ${GIT2CPP_SOURCE_DIR}/utils/common.hpp ${GIT2CPP_SOURCE_DIR}/utils/git_exception.cpp ${GIT2CPP_SOURCE_DIR}/utils/git_exception.hpp + ${GIT2CPP_SOURCE_DIR}/wrapper/annotated_commit_wrapper.cpp + ${GIT2CPP_SOURCE_DIR}/wrapper/annotated_commit_wrapper.hpp ${GIT2CPP_SOURCE_DIR}/wrapper/branch_wrapper.cpp ${GIT2CPP_SOURCE_DIR}/wrapper/branch_wrapper.hpp ${GIT2CPP_SOURCE_DIR}/wrapper/commit_wrapper.cpp diff --git a/src/wrapper/annotated_commit_wrapper.cpp b/src/wrapper/annotated_commit_wrapper.cpp new file mode 100644 index 0000000..da38620 --- /dev/null +++ b/src/wrapper/annotated_commit_wrapper.cpp @@ -0,0 +1,23 @@ +#include "../wrapper/annotated_commit_wrapper.hpp" + +annotated_commit_wrapper::annotated_commit_wrapper(git_annotated_commit* commit) + : base_type(commit) +{ +} + +annotated_commit_wrapper::~annotated_commit_wrapper() +{ + git_annotated_commit_free(p_resource); + p_resource = nullptr; +} + +const git_oid& annotated_commit_wrapper::oid() const +{ + return *git_annotated_commit_id(p_resource); +} + +std::string_view annotated_commit_wrapper::reference_name() const +{ + const char* res = git_annotated_commit_ref(*this); + return res ? res : std::string_view{}; +} diff --git a/src/wrapper/annotated_commit_wrapper.hpp b/src/wrapper/annotated_commit_wrapper.hpp new file mode 100644 index 0000000..7bb5a4c --- /dev/null +++ b/src/wrapper/annotated_commit_wrapper.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +#include "../wrapper/wrapper_base.hpp" + +class annotated_commit_wrapper : public wrapper_base +{ +public: + + using base_type = wrapper_base; + + ~annotated_commit_wrapper(); + + annotated_commit_wrapper(annotated_commit_wrapper&&) noexcept = default; + annotated_commit_wrapper& operator=(annotated_commit_wrapper&&) noexcept = default; + + const git_oid& oid() const; + std::string_view reference_name() const; + +private: + + annotated_commit_wrapper(git_annotated_commit* commit); + + friend class repository_wrapper; +}; + diff --git a/src/wrapper/commit_wrapper.cpp b/src/wrapper/commit_wrapper.cpp index 02dee81..c67900c 100644 --- a/src/wrapper/commit_wrapper.cpp +++ b/src/wrapper/commit_wrapper.cpp @@ -11,12 +11,8 @@ commit_wrapper::~commit_wrapper() p_resource = nullptr; } -/*commit_wrapper commit_wrapper::from_reference_name(const repository_wrapper& repo, const std::string& ref_name) +const git_oid& commit_wrapper::oid() const { - git_oid oid_parent_commit; - throwIfError(git_reference_name_to_id(&oid_parent_commit, repo, ref_name.c_str())); + return *git_commit_id(p_resource); +} - commit_wrapper cw; - throwIfError(git_commit_lookup(&(cw.p_resource), repo, &oid_parent_commit)); - return cw; -}*/ diff --git a/src/wrapper/commit_wrapper.hpp b/src/wrapper/commit_wrapper.hpp index 4c27aaf..f953711 100644 --- a/src/wrapper/commit_wrapper.hpp +++ b/src/wrapper/commit_wrapper.hpp @@ -15,6 +15,8 @@ class commit_wrapper : public wrapper_base commit_wrapper(commit_wrapper&&) noexcept = default; commit_wrapper& operator=(commit_wrapper&&) noexcept = default; + const git_oid& oid() const; + private: commit_wrapper(git_commit* commit); diff --git a/src/wrapper/repository_wrapper.cpp b/src/wrapper/repository_wrapper.cpp index 7cbac9f..43de047 100644 --- a/src/wrapper/repository_wrapper.cpp +++ b/src/wrapper/repository_wrapper.cpp @@ -28,6 +28,13 @@ reference_wrapper repository_wrapper::head() const return reference_wrapper(ref); } +reference_wrapper repository_wrapper::find_reference(std::string_view ref_name) const +{ + git_reference* ref; + throwIfError(git_reference_lookup(&ref, *this, ref_name.data())); + return reference_wrapper(ref); +} + index_wrapper repository_wrapper::make_index() { index_wrapper index = index_wrapper::init(*this); @@ -46,7 +53,7 @@ branch_wrapper repository_wrapper::create_branch(std::string_view name, const co return branch_wrapper(branch); } -branch_wrapper repository_wrapper::find_branch(std::string_view name) +branch_wrapper repository_wrapper::find_branch(std::string_view name) const { git_reference* branch = nullptr; throwIfError(git_branch_lookup(&branch, *this, name.data(), GIT_BRANCH_LOCAL)); @@ -74,3 +81,10 @@ commit_wrapper repository_wrapper::find_commit(const git_oid& id) const throwIfError(git_commit_lookup(&commit, *this, &id)); return commit_wrapper(commit); } + +annotated_commit_wrapper repository_wrapper::find_annotated_commit(const git_oid& id) const +{ + git_annotated_commit* commit; + throwIfError(git_annotated_commit_lookup(&commit, *this, &id)); + return annotated_commit_wrapper(commit); +} diff --git a/src/wrapper/repository_wrapper.hpp b/src/wrapper/repository_wrapper.hpp index 9e3e03c..14c6b29 100644 --- a/src/wrapper/repository_wrapper.hpp +++ b/src/wrapper/repository_wrapper.hpp @@ -4,6 +4,7 @@ #include +#include "../wrapper/annotated_commit_wrapper.hpp" #include "../wrapper/branch_wrapper.hpp" #include "../wrapper/commit_wrapper.hpp" #include "../wrapper/index_wrapper.hpp" @@ -22,22 +23,26 @@ class repository_wrapper : public wrapper_base static repository_wrapper init(std::string_view directory, bool bare); static repository_wrapper open(std::string_view directory); + // References reference_wrapper head() const; + reference_wrapper find_reference(std::string_view ref_name) const; + // Index index_wrapper make_index(); + // Branches branch_wrapper create_branch(std::string_view name, bool force); branch_wrapper create_branch(std::string_view name, const commit_wrapper& commit, bool force); - - branch_wrapper find_branch(std::string_view name); - + branch_wrapper find_branch(std::string_view name) const; branch_iterator iterate_branches(git_branch_t type) const; // Commits - commit_wrapper find_commit(std::string_view ref_name = "HEAD") const; commit_wrapper find_commit(const git_oid& id) const; + // Annotated commits + annotated_commit_wrapper find_annotated_commit(const git_oid& id) const; + private: repository_wrapper() = default; From acbdf2e41d210ecfe4ff02d9c1594729d3adea27 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 22 Jul 2025 03:49:30 +0200 Subject: [PATCH 4/4] Minimal implementation of checkout subcommand --- CMakeLists.txt | 4 + src/main.cpp | 2 + src/subcommand/checkout_subcommand.cpp | 127 +++++++++++++++++++++++++ src/subcommand/checkout_subcommand.hpp | 52 ++++++++++ src/wrapper/branch_wrapper.cpp | 8 +- src/wrapper/branch_wrapper.hpp | 1 + src/wrapper/commit_wrapper.cpp | 5 + src/wrapper/commit_wrapper.hpp | 2 + src/wrapper/object_wrapper.cpp | 17 ++++ src/wrapper/object_wrapper.hpp | 25 +++++ src/wrapper/refs_wrapper.cpp | 5 + src/wrapper/refs_wrapper.hpp | 3 +- src/wrapper/repository_wrapper.cpp | 37 ++++++- src/wrapper/repository_wrapper.hpp | 28 ++++++ test/conftest.py | 8 +- test/test_branch.py | 6 -- test/test_checkout.py | 39 ++++++++ 17 files changed, 358 insertions(+), 11 deletions(-) create mode 100644 src/subcommand/checkout_subcommand.cpp create mode 100644 src/subcommand/checkout_subcommand.hpp create mode 100644 src/wrapper/object_wrapper.cpp create mode 100644 src/wrapper/object_wrapper.hpp create mode 100644 test/test_checkout.py diff --git a/CMakeLists.txt b/CMakeLists.txt index a4e8f56..90e3246 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,8 @@ set(GIT2CPP_SRC ${GIT2CPP_SOURCE_DIR}/subcommand/add_subcommand.hpp ${GIT2CPP_SOURCE_DIR}/subcommand/branch_subcommand.cpp ${GIT2CPP_SOURCE_DIR}/subcommand/branch_subcommand.hpp + ${GIT2CPP_SOURCE_DIR}/subcommand/checkout_subcommand.cpp + ${GIT2CPP_SOURCE_DIR}/subcommand/checkout_subcommand.hpp ${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.cpp ${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.hpp ${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp @@ -59,6 +61,8 @@ set(GIT2CPP_SRC ${GIT2CPP_SOURCE_DIR}/wrapper/commit_wrapper.hpp ${GIT2CPP_SOURCE_DIR}/wrapper/index_wrapper.cpp ${GIT2CPP_SOURCE_DIR}/wrapper/index_wrapper.hpp + ${GIT2CPP_SOURCE_DIR}/wrapper/object_wrapper.cpp + ${GIT2CPP_SOURCE_DIR}/wrapper/object_wrapper.hpp ${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.cpp ${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.hpp ${GIT2CPP_SOURCE_DIR}/wrapper/repository_wrapper.cpp diff --git a/src/main.cpp b/src/main.cpp index a04444a..55e8a24 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include "version.hpp" #include "subcommand/add_subcommand.hpp" #include "subcommand/branch_subcommand.hpp" +#include "subcommand/checkout_subcommand.hpp" #include "subcommand/init_subcommand.hpp" #include "subcommand/status_subcommand.hpp" @@ -25,6 +26,7 @@ int main(int argc, char** argv) status_subcommand status(lg2_obj, app); add_subcommand add(lg2_obj, app); branch_subcommand(lg2_obj, app); + checkout_subcommand(lg2_obj, app); app.parse(argc, argv); diff --git a/src/subcommand/checkout_subcommand.cpp b/src/subcommand/checkout_subcommand.cpp new file mode 100644 index 0000000..67b8644 --- /dev/null +++ b/src/subcommand/checkout_subcommand.cpp @@ -0,0 +1,127 @@ +#include +#include + +#include "../subcommand/checkout_subcommand.hpp" +#include "../utils/git_exception.hpp" +#include "../wrapper/repository_wrapper.hpp" + +checkout_subcommand::checkout_subcommand(const libgit2_object&, CLI::App& app) +{ + auto* sub = app.add_subcommand("checkout", "Switch branches or restore working tree files"); + + sub->add_option("", m_branch_name, "Branch to checkout"); + sub->add_flag("-b", m_create_flag, "Create a new branch before checking it out"); + sub->add_flag("-B", m_force_create_flag, "Create a new branch or reset it if it exists before checking it out"); + sub->add_flag("-f, --force", m_force_checkout_flag, "When switching branches, proceed even if the index or the working tree differs from HEAD, and even if there are untracked files in the way"); + + sub->callback([this]() { this->run(); }); +} + +void checkout_subcommand::run() +{ + auto directory = get_current_git_path(); + auto repo = repository_wrapper::open(directory); + + if (repo.state() != GIT_REPOSITORY_STATE_NONE) + { + throw std::runtime_error("Cannot checkout, repository is in unexpected state"); + } + + git_checkout_options options; + git_checkout_options_init(&options, GIT_CHECKOUT_OPTIONS_VERSION); + + if(m_force_checkout_flag) + { + options.checkout_strategy = GIT_CHECKOUT_FORCE; + } + + if (m_create_flag || m_force_create_flag) + { + auto annotated_commit = create_local_branch(repo, m_branch_name, m_force_create_flag); + checkout_tree(repo, annotated_commit, m_branch_name, options); + update_head(repo, annotated_commit, m_branch_name); + } + else + { + auto optional_commit = resolve_local_ref(repo, m_branch_name); + if (!optional_commit) + { + // TODO: handle remote refs + std::ostringstream buffer; + buffer << "error: could not resolve pathspec '" << m_branch_name << "'" << std::endl; + throw std::runtime_error(buffer.str()); + } + checkout_tree(repo, *optional_commit, m_branch_name, options); + update_head(repo, *optional_commit, m_branch_name); + } +} + +std::optional checkout_subcommand::resolve_local_ref +( + const repository_wrapper& repo, + const std::string& target_name +) +{ + if (auto ref = repo.find_reference_dwim(target_name)) + { + return repo.find_annotated_commit(*ref); + } + else if (auto obj = repo.revparse_single(target_name)) + { + return repo.find_annotated_commit(obj->oid()); + } + else + { + return std::nullopt; + } +} + +annotated_commit_wrapper checkout_subcommand::create_local_branch +( + repository_wrapper& repo, + const std::string& target_name, + bool force +) +{ + auto branch = repo.create_branch(target_name, force); + return repo.find_annotated_commit(branch); +} + +void checkout_subcommand::checkout_tree +( + const repository_wrapper& repo, + const annotated_commit_wrapper& target_annotated_commit, + const std::string& target_name, + const git_checkout_options& options +) +{ + auto target_commit = repo.find_commit(target_annotated_commit.oid()); + throwIfError(git_checkout_tree(repo, target_commit, &options)); +} + +void checkout_subcommand::update_head +( + repository_wrapper& repo, + const annotated_commit_wrapper& target_annotated_commit, + const std::string& target_name +) +{ + std::string_view annotated_ref = target_annotated_commit.reference_name(); + if (!annotated_ref.empty()) + { + auto ref = repo.find_reference(annotated_ref); + if (ref.is_remote()) + { + auto branch = repo.create_branch(target_name, target_annotated_commit); + repo.set_head(branch.reference_name()); + } + else + { + repo.set_head(annotated_ref); + } + } + else + { + repo.set_head_detached(target_annotated_commit); + } +} diff --git a/src/subcommand/checkout_subcommand.hpp b/src/subcommand/checkout_subcommand.hpp new file mode 100644 index 0000000..533b46a --- /dev/null +++ b/src/subcommand/checkout_subcommand.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#include + +#include "../utils/common.hpp" +#include "../wrapper/repository_wrapper.hpp" + +class checkout_subcommand +{ +public: + + explicit checkout_subcommand(const libgit2_object&, CLI::App& app); + void run(); + +private: + + std::optional resolve_local_ref + ( + const repository_wrapper& repo, + const std::string& target_name + ); + + annotated_commit_wrapper create_local_branch + ( + repository_wrapper& repo, + const std::string& target_name, + bool force + ); + + void checkout_tree + ( + const repository_wrapper& repo, + const annotated_commit_wrapper& target_annotated_commit, + const std::string& target_name, + const git_checkout_options& options + ); + + void update_head + ( + repository_wrapper& repo, + const annotated_commit_wrapper& target_annotated_commit, + const std::string& target_name + ); + + std::string m_branch_name = {}; + bool m_create_flag = false; + bool m_force_create_flag = false; + bool m_force_checkout_flag = false; +}; diff --git a/src/wrapper/branch_wrapper.cpp b/src/wrapper/branch_wrapper.cpp index afc5d64..bc0af64 100644 --- a/src/wrapper/branch_wrapper.cpp +++ b/src/wrapper/branch_wrapper.cpp @@ -19,10 +19,16 @@ branch_wrapper::~branch_wrapper() std::string_view branch_wrapper::name() const { const char* out = nullptr; - throwIfError(git_branch_name(&out, p_resource)); + throwIfError(git_branch_name(&out, *this)); return std::string_view(out); } +std::string_view branch_wrapper::reference_name() const +{ + const char* out = git_reference_name(*this); + return out ? out : std::string_view(); +} + void delete_branch(branch_wrapper&& branch) { throwIfError(git_branch_delete(branch)); diff --git a/src/wrapper/branch_wrapper.hpp b/src/wrapper/branch_wrapper.hpp index 436c9e4..d73bad0 100644 --- a/src/wrapper/branch_wrapper.hpp +++ b/src/wrapper/branch_wrapper.hpp @@ -21,6 +21,7 @@ class branch_wrapper : public wrapper_base branch_wrapper& operator=(branch_wrapper&&) = default; std::string_view name() const; + std::string_view reference_name() const; private: diff --git a/src/wrapper/commit_wrapper.cpp b/src/wrapper/commit_wrapper.cpp index c67900c..66fcd4f 100644 --- a/src/wrapper/commit_wrapper.cpp +++ b/src/wrapper/commit_wrapper.cpp @@ -11,6 +11,11 @@ commit_wrapper::~commit_wrapper() p_resource = nullptr; } +commit_wrapper::operator git_object*() const noexcept +{ + return reinterpret_cast(p_resource); +} + const git_oid& commit_wrapper::oid() const { return *git_commit_id(p_resource); diff --git a/src/wrapper/commit_wrapper.hpp b/src/wrapper/commit_wrapper.hpp index f953711..201cbce 100644 --- a/src/wrapper/commit_wrapper.hpp +++ b/src/wrapper/commit_wrapper.hpp @@ -15,6 +15,8 @@ class commit_wrapper : public wrapper_base commit_wrapper(commit_wrapper&&) noexcept = default; commit_wrapper& operator=(commit_wrapper&&) noexcept = default; + operator git_object*() const noexcept; + const git_oid& oid() const; private: diff --git a/src/wrapper/object_wrapper.cpp b/src/wrapper/object_wrapper.cpp new file mode 100644 index 0000000..cea6175 --- /dev/null +++ b/src/wrapper/object_wrapper.cpp @@ -0,0 +1,17 @@ +#include "../wrapper/object_wrapper.hpp" + +object_wrapper::object_wrapper(git_object* obj) + : base_type(obj) +{ +} + +object_wrapper::~object_wrapper() +{ + git_object_free(p_resource); + p_resource = nullptr; +} + +const git_oid& object_wrapper::oid() const +{ + return *git_object_id(*this); +} diff --git a/src/wrapper/object_wrapper.hpp b/src/wrapper/object_wrapper.hpp new file mode 100644 index 0000000..b0b86bf --- /dev/null +++ b/src/wrapper/object_wrapper.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "../wrapper/wrapper_base.hpp" + +class object_wrapper : public wrapper_base +{ +public: + + using base_type = wrapper_base; + + ~object_wrapper(); + + object_wrapper(object_wrapper&&) noexcept = default; + object_wrapper& operator=(object_wrapper&&) noexcept = default; + + const git_oid& oid() const; + +private: + + object_wrapper(git_object* obj); + + friend class repository_wrapper; +}; diff --git a/src/wrapper/refs_wrapper.cpp b/src/wrapper/refs_wrapper.cpp index 21ef84b..571ca52 100644 --- a/src/wrapper/refs_wrapper.cpp +++ b/src/wrapper/refs_wrapper.cpp @@ -16,3 +16,8 @@ std::string reference_wrapper::short_name() const { return git_reference_shorthand(p_resource); } + +bool reference_wrapper::is_remote() const +{ + return git_reference_is_remote(*this); +} diff --git a/src/wrapper/refs_wrapper.hpp b/src/wrapper/refs_wrapper.hpp index dfe32a7..2ad4b21 100644 --- a/src/wrapper/refs_wrapper.hpp +++ b/src/wrapper/refs_wrapper.hpp @@ -1,6 +1,6 @@ #pragma once - #include +#include #include @@ -18,6 +18,7 @@ class reference_wrapper : public wrapper_base reference_wrapper& operator=(reference_wrapper&&) noexcept = default; std::string short_name() const; + bool is_remote() const; private: diff --git a/src/wrapper/repository_wrapper.cpp b/src/wrapper/repository_wrapper.cpp index 43de047..9c9a0e0 100644 --- a/src/wrapper/repository_wrapper.cpp +++ b/src/wrapper/repository_wrapper.cpp @@ -21,6 +21,11 @@ repository_wrapper repository_wrapper::init(std::string_view directory, bool bar return rw; } +git_repository_state_t repository_wrapper::state() const +{ + return git_repository_state_t(git_repository_state(*this)); +} + reference_wrapper repository_wrapper::head() const { git_reference* ref; @@ -35,6 +40,13 @@ reference_wrapper repository_wrapper::find_reference(std::string_view ref_name) return reference_wrapper(ref); } +std::optional repository_wrapper::find_reference_dwim(std::string_view ref_name) const +{ + git_reference* ref; + int rc = git_reference_dwim(&ref, *this, ref_name.data()); + return rc == 0 ? std::make_optional(reference_wrapper(ref)) : std::nullopt; +} + index_wrapper repository_wrapper::make_index() { index_wrapper index = index_wrapper::init(*this); @@ -53,6 +65,13 @@ branch_wrapper repository_wrapper::create_branch(std::string_view name, const co return branch_wrapper(branch); } +branch_wrapper repository_wrapper::create_branch(std::string_view name, const annotated_commit_wrapper& commit, bool force) +{ + git_reference* branch = nullptr; + throwIfError(git_branch_create_from_annotated(&branch, *this, name.data(), commit, force)); + return branch_wrapper(branch); +} + branch_wrapper repository_wrapper::find_branch(std::string_view name) const { git_reference* branch = nullptr; @@ -67,7 +86,6 @@ branch_iterator repository_wrapper::iterate_branches(git_branch_t type) const return branch_iterator(iter); } - commit_wrapper repository_wrapper::find_commit(std::string_view ref_name) const { git_oid oid_parent_commit; @@ -88,3 +106,20 @@ annotated_commit_wrapper repository_wrapper::find_annotated_commit(const git_oid throwIfError(git_annotated_commit_lookup(&commit, *this, &id)); return annotated_commit_wrapper(commit); } + +std::optional repository_wrapper::revparse_single(std::string_view spec) const +{ + git_object* obj; + int rc = git_revparse_single(&obj, *this, spec.data()); + return rc == 0 ? std::make_optional(object_wrapper(obj)) : std::nullopt; +} + +void repository_wrapper::set_head(std::string_view ref_name) +{ + throwIfError(git_repository_set_head(*this, ref_name.data())); +} + +void repository_wrapper::set_head_detached(const annotated_commit_wrapper& commit) +{ + throwIfError(git_repository_set_head_detached_from_annotated(*this, commit)); +} diff --git a/src/wrapper/repository_wrapper.hpp b/src/wrapper/repository_wrapper.hpp index 14c6b29..a33c4a3 100644 --- a/src/wrapper/repository_wrapper.hpp +++ b/src/wrapper/repository_wrapper.hpp @@ -1,13 +1,17 @@ #pragma once +#include +#include #include #include +#include "../utils/git_exception.hpp" #include "../wrapper/annotated_commit_wrapper.hpp" #include "../wrapper/branch_wrapper.hpp" #include "../wrapper/commit_wrapper.hpp" #include "../wrapper/index_wrapper.hpp" +#include "../wrapper/object_wrapper.hpp" #include "../wrapper/refs_wrapper.hpp" #include "../wrapper/wrapper_base.hpp" @@ -23,9 +27,12 @@ class repository_wrapper : public wrapper_base static repository_wrapper init(std::string_view directory, bool bare); static repository_wrapper open(std::string_view directory); + git_repository_state_t state() const; + // References reference_wrapper head() const; reference_wrapper find_reference(std::string_view ref_name) const; + std::optional find_reference_dwim(std::string_view ref_name) const; // Index index_wrapper make_index(); @@ -33,7 +40,10 @@ class repository_wrapper : public wrapper_base // Branches branch_wrapper create_branch(std::string_view name, bool force); branch_wrapper create_branch(std::string_view name, const commit_wrapper& commit, bool force); + branch_wrapper create_branch(std::string_view name, const annotated_commit_wrapper& commit, bool force); + branch_wrapper find_branch(std::string_view name) const; + branch_iterator iterate_branches(git_branch_t type) const; // Commits @@ -43,7 +53,25 @@ class repository_wrapper : public wrapper_base // Annotated commits annotated_commit_wrapper find_annotated_commit(const git_oid& id) const; + template T> + annotated_commit_wrapper find_annotated_commit(const T& wrapper) const; + + // Objects + std::optional revparse_single(std::string_view spec) const; + + // Set head + void set_head(std::string_view ref_name); + void set_head_detached(const annotated_commit_wrapper& commit); + private: repository_wrapper() = default; }; + +template T> +annotated_commit_wrapper repository_wrapper::find_annotated_commit(const T& wrapper) const +{ + git_annotated_commit* commit; + throwIfError(git_annotated_commit_from_ref(&commit, *this, wrapper)); + return annotated_commit_wrapper(commit); +} diff --git a/test/conftest.py b/test/conftest.py index 5f8435e..75c398e 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -2,7 +2,6 @@ from pathlib import Path import pytest - # Fixture to run test in current tmp_path @pytest.fixture def run_in_tmp_path(tmp_path): @@ -11,7 +10,12 @@ def run_in_tmp_path(tmp_path): yield os.chdir(original_cwd) - @pytest.fixture(scope='session') def git2cpp_path(): return Path(__file__).parent.parent / 'build' / 'git2cpp' + +@pytest.fixture +def rename_git(): + os.rename("test/data/status_data/embedded_git/", "test/data/status_data/.git/") + yield + os.rename("test/data/status_data/.git/", "test/data/status_data/embedded_git/") diff --git a/test/test_branch.py b/test/test_branch.py index c6d5882..5265a62 100644 --- a/test/test_branch.py +++ b/test/test_branch.py @@ -3,12 +3,6 @@ import pytest -@pytest.fixture -def rename_git(): - os.rename("test/data/status_data/embedded_git/", "test/data/status_data/.git/") - yield - os.rename("test/data/status_data/.git/", "test/data/status_data/embedded_git/") - def test_branch_list(rename_git, git2cpp_path): cmd = [git2cpp_path, 'branch'] p = subprocess.run(cmd, capture_output=True, cwd="test/data/status_data", text=True) diff --git a/test/test_checkout.py b/test/test_checkout.py new file mode 100644 index 0000000..857d5e3 --- /dev/null +++ b/test/test_checkout.py @@ -0,0 +1,39 @@ +import os +import subprocess + +import pytest + +def test_checkout(rename_git, git2cpp_path): + create_cmd = [git2cpp_path, 'branch', 'foregone'] + subprocess.run(create_cmd, capture_output=True, cwd="test/data/status_data", text=True) + + checkout_cmd = [git2cpp_path, 'checkout', 'foregone'] + p = subprocess.run(checkout_cmd, capture_output=True, cwd="test/data/status_data", text=True) + assert(p.stdout == ''); + + branch_cmd = [git2cpp_path, 'branch'] + p2 = subprocess.run(branch_cmd, capture_output=True, cwd="test/data/status_data", text=True) + assert(p2.stdout == '* foregone\n main\n') + + checkout_cmd[2] = 'main' + subprocess.run(checkout_cmd, capture_output=True, cwd="test/data/status_data", text=True) + + del_cmd = [git2cpp_path, 'branch', '-d', 'foregone'] + subprocess.run(del_cmd, cwd="test/data/status_data", text=True) + +def test_checkout_b(rename_git, git2cpp_path): + checkout_cmd = [git2cpp_path, 'checkout', '-b', 'foregone'] + p = subprocess.run(checkout_cmd, capture_output=True, cwd="test/data/status_data", text=True) + assert(p.stdout == ''); + + branch_cmd = [git2cpp_path, 'branch'] + p2 = subprocess.run(branch_cmd, capture_output=True, cwd="test/data/status_data", text=True) + assert(p2.stdout == '* foregone\n main\n') + + checkout_cmd.remove('-b') + checkout_cmd[2] = 'main' + subprocess.run(checkout_cmd, cwd="test/data/status_data", text=True) + + del_cmd = [git2cpp_path, 'branch', '-d', 'foregone'] + subprocess.run(del_cmd, cwd="test/data/status_data", text=True) +