From 2ed957e8dee57c585863e1e7ca5b6c30077feeeb Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Mon, 1 Sep 2025 17:27:53 +0200 Subject: [PATCH 1/4] Add merge subcommand (fastforward) --- CMakeLists.txt | 2 + src/main.cpp | 3 + src/subcommand/checkout_subcommand.cpp | 2 +- src/subcommand/commit_subcommand.cpp | 2 +- src/subcommand/merge_subcommand.cpp | 182 +++++++++++++++++++++++ src/subcommand/merge_subcommand.hpp | 16 ++ src/wrapper/annotated_commit_wrapper.cpp | 36 +++++ src/wrapper/annotated_commit_wrapper.hpp | 23 +++ src/wrapper/commit_wrapper.cpp | 20 +++ src/wrapper/commit_wrapper.hpp | 24 ++- src/wrapper/refs_wrapper.cpp | 15 ++ src/wrapper/refs_wrapper.hpp | 4 + src/wrapper/repository_wrapper.cpp | 82 ++++++++-- src/wrapper/repository_wrapper.hpp | 7 +- src/wrapper/signature_wrapper.cpp | 20 +++ src/wrapper/signature_wrapper.hpp | 7 +- test/conftest.py | 15 +- test/test_merge.py | 47 ++++++ 18 files changed, 486 insertions(+), 21 deletions(-) create mode 100644 src/subcommand/merge_subcommand.cpp create mode 100644 src/subcommand/merge_subcommand.hpp create mode 100644 test/test_merge.py diff --git a/CMakeLists.txt b/CMakeLists.txt index ba71a22..d18cbd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,8 @@ set(GIT2CPP_SRC ${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.hpp ${GIT2CPP_SOURCE_DIR}/subcommand/log_subcommand.cpp ${GIT2CPP_SOURCE_DIR}/subcommand/log_subcommand.hpp + ${GIT2CPP_SOURCE_DIR}/subcommand/merge_subcommand.cpp + ${GIT2CPP_SOURCE_DIR}/subcommand/merge_subcommand.hpp ${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.cpp ${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.hpp ${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp diff --git a/src/main.cpp b/src/main.cpp index 71140ad..e8479c8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include +#include #include // For version number only #include @@ -11,6 +12,7 @@ #include "subcommand/commit_subcommand.hpp" #include "subcommand/init_subcommand.hpp" #include "subcommand/log_subcommand.hpp" +#include "subcommand/merge_subcommand.hpp" #include "subcommand/reset_subcommand.hpp" #include "subcommand/status_subcommand.hpp" @@ -35,6 +37,7 @@ int main(int argc, char** argv) commit_subcommand commit(lg2_obj, app); reset_subcommand reset(lg2_obj, app); log_subcommand log(lg2_obj, app); + merge_subcommand merge(lg2_obj, app); app.require_subcommand(/* min */ 0, /* max */ 1); diff --git a/src/subcommand/checkout_subcommand.cpp b/src/subcommand/checkout_subcommand.cpp index 09491b8..6ae3e42 100644 --- a/src/subcommand/checkout_subcommand.cpp +++ b/src/subcommand/checkout_subcommand.cpp @@ -43,7 +43,7 @@ void checkout_subcommand::run() } else { - auto optional_commit = resolve_local_ref(repo, m_branch_name); + auto optional_commit = repo.resolve_local_ref(m_branch_name); if (!optional_commit) { // TODO: handle remote refs diff --git a/src/subcommand/commit_subcommand.cpp b/src/subcommand/commit_subcommand.cpp index d281ec6..bca7f39 100644 --- a/src/subcommand/commit_subcommand.cpp +++ b/src/subcommand/commit_subcommand.cpp @@ -32,5 +32,5 @@ void commit_subcommand::run() } } - repo.create_commit(author_committer_signatures, m_commit_message); + repo.create_commit(author_committer_signatures, m_commit_message, std::nullopt); } diff --git a/src/subcommand/merge_subcommand.cpp b/src/subcommand/merge_subcommand.cpp new file mode 100644 index 0000000..5e6ce30 --- /dev/null +++ b/src/subcommand/merge_subcommand.cpp @@ -0,0 +1,182 @@ +#include +#include + +#include "merge_subcommand.hpp" +#include "../wrapper/repository_wrapper.hpp" + + +merge_subcommand::merge_subcommand(const libgit2_object&, CLI::App& app) +{ + auto *sub = app.add_subcommand("merge", "Join two or more development histories together"); + + sub->add_option("", m_branches_to_merge, "Branch(es) to merge"); + + sub->callback([this]() { this->run(); }); +} + +annotated_commit_list_wrapper resolve_heads(const repository_wrapper& repo, std::vector m_branches_to_merge) +{ + std::vector commits_to_merge; + commits_to_merge.reserve(m_branches_to_merge.size()); + + for (const auto branch_name:m_branches_to_merge) + { + std::optional commit = repo.resolve_local_ref(branch_name); + if (commit.has_value()) + { + commits_to_merge.push_back(std::move(commit).value()); + } + } + return annotated_commit_list_wrapper(std::move(commits_to_merge)); +} + +void perform_fastforward(repository_wrapper& repo, const git_oid* target_oid, int is_unborn) +{ + const git_checkout_options ff_checkout_options = GIT_CHECKOUT_OPTIONS_INIT; + + auto lambda_get_target_ref = [] (auto repo, auto is_unborn) + { + if (!is_unborn) + { + return repo->head(); + } + else + { + return repo->find_reference("HEAD"); + } + }; + auto target_ref = lambda_get_target_ref(&repo, is_unborn); + + auto target = repo.find_object(target_oid, GIT_OBJECT_COMMIT); + + repo.checkout_tree(target, &ff_checkout_options); + + auto new_target_ref = target_ref.new_ref(target_oid); +} + +// static void create_merge_commit(repository_wrapper repo, index_wrapper index, std::vector m_branches_to_merge, +// std::vector commits_to_merge) +// { +// auto head_ref = repo.head(); +// auto merge_ref = repo.find_reference_dwim(m_branches_to_merge.front()); +// // if (ref) +// // { +// // auto merge_ref = std::move(ref).value(); +// // } +// auto merge_commit = repo.resolve_local_ref(m_branches_to_merge.front()).value(); + +// size_t annotated_count = commits_to_merge.size(); +// std::vector parents_list; +// parents_list.reserve(annotated_count + 1); +// parents_list.push_back(std::move(head_ref.peel())); +// for (size_t i=0; ishort_name(); +// } +// else +// { +// msg_target = git_oid_tostr_s(&(merge_commit.oid())); +// } + +// std::string msg; +// msg = "Merge "; +// if (merge_ref) +// { +// msg.append("branch "); +// } +// else +// { +// msg.append("commit "); +// } +// msg.append(msg_target); +// std::cout << msg << std::endl; + +// repo.create_commit(author_committer_sign_now, msg, std::optional(std::move(parents))); +// } + +void merge_subcommand::run() +{ + auto directory = get_current_git_path(); + auto bare = false; + auto repo = repository_wrapper::open(directory); + + auto state = repo.state(); + if (state != GIT_REPOSITORY_STATE_NONE) + { + std::cout << "repository is in unexpected state " << state <(c_commits_to_merge); + + throw_if_error(git_merge_analysis(&analysis, &preference, repo, commits_to_merge_const, num_commits_to_merge)); + + if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) + { + std::cout << "Already up-to-date" << std::endl; + } + else if (analysis & GIT_MERGE_ANALYSIS_UNBORN || + (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD && + !(preference & GIT_MERGE_PREFERENCE_NO_FASTFORWARD))) + { + const git_oid* target_oid; + if (analysis & GIT_MERGE_ANALYSIS_UNBORN) + { + std::cout << "Unborn" << std::endl; + } + else + { + std::cout << "Fast-forward" << std::endl; + } + const annotated_commit_wrapper& commit = commits_to_merge.front(); + target_oid = &commit.oid(); + assert(num_commits_to_merge == 1); + perform_fastforward(repo, target_oid, (analysis & GIT_MERGE_ANALYSIS_UNBORN)); + } + else if (analysis & GIT_MERGE_ANALYSIS_NORMAL) + { + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + merge_opts.flags = 0; + merge_opts.file_flags = GIT_MERGE_FILE_STYLE_DIFF3; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE|GIT_CHECKOUT_ALLOW_CONFLICTS; + + if (preference & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY) + { + std::cout << "Fast-forward is preferred, but only a merge is possible\n" << std::endl; + // how to break ? + } + + // git_merge(repo, + // (const git_annotated_commit **)opts.annotated, opts.annotated_count, + // &merge_opts, &checkout_opts); + } + + // if (git_index_has_conflicts(index)) { + // /* Handle conflicts */ + // output_conflicts(index); + // } else if (!opts.no_commit) { + // create_merge_commit(repo, index, &opts); + // printf("Merge made\n"); + // } +} diff --git a/src/subcommand/merge_subcommand.hpp b/src/subcommand/merge_subcommand.hpp new file mode 100644 index 0000000..306d65d --- /dev/null +++ b/src/subcommand/merge_subcommand.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "../utils/common.hpp" + +class merge_subcommand +{ +public: + + explicit merge_subcommand(const libgit2_object&, CLI::App& app); + void run(); + +private: + std::vector m_branches_to_merge; +}; diff --git a/src/wrapper/annotated_commit_wrapper.cpp b/src/wrapper/annotated_commit_wrapper.cpp index da38620..a3f89e2 100644 --- a/src/wrapper/annotated_commit_wrapper.cpp +++ b/src/wrapper/annotated_commit_wrapper.cpp @@ -1,4 +1,5 @@ #include "../wrapper/annotated_commit_wrapper.hpp" +#include annotated_commit_wrapper::annotated_commit_wrapper(git_annotated_commit* commit) : base_type(commit) @@ -21,3 +22,38 @@ std::string_view annotated_commit_wrapper::reference_name() const const char* res = git_annotated_commit_ref(*this); return res ? res : std::string_view{}; } + +annotated_commit_list_wrapper::annotated_commit_list_wrapper(std::vector annotated_commit_list) + : m_annotated_commit_list(std::move(annotated_commit_list)) +{ + p_resource = new git_annotated_commit*[m_annotated_commit_list.size()]; + for (size_t i=0; i +#include #include @@ -27,3 +28,25 @@ class annotated_commit_wrapper : public wrapper_base friend class repository_wrapper; }; +class annotated_commit_list_wrapper : public wrapper_base +{ +public: + + using base_type = wrapper_base; + + explicit annotated_commit_list_wrapper(std::vector annotated_commit_list); + + ~annotated_commit_list_wrapper(); + + annotated_commit_list_wrapper(annotated_commit_list_wrapper&&) noexcept = default; + annotated_commit_list_wrapper& operator=(annotated_commit_list_wrapper&&) noexcept = default; + + size_t size() const; + annotated_commit_wrapper front(); + void print() const; + +private: + + std::vector m_annotated_commit_list; + +}; diff --git a/src/wrapper/commit_wrapper.cpp b/src/wrapper/commit_wrapper.cpp index a5996fc..f079e5b 100644 --- a/src/wrapper/commit_wrapper.cpp +++ b/src/wrapper/commit_wrapper.cpp @@ -26,3 +26,23 @@ std::string commit_wrapper::commit_oid_tostr() const char buf[GIT_OID_SHA1_HEXSIZE + 1]; return git_oid_tostr(buf, sizeof(buf), &this->oid()); } + +commit_list_wrapper::commit_list_wrapper(std::vector commit_list) +{ + git_commit** p_resource = new git_commit*[m_commit_list.size()]; + for (size_t i=0; i +#include +#include -#include "../wrapper/repository_wrapper.hpp" #include "../wrapper/wrapper_base.hpp" class commit_wrapper : public wrapper_base @@ -28,3 +29,24 @@ class commit_wrapper : public wrapper_base friend class repository_wrapper; friend class reference_wrapper; }; + +class commit_list_wrapper : public wrapper_base +{ +public: + + using base_type = wrapper_base; + + explicit commit_list_wrapper(std::vector commit_list); + + ~commit_list_wrapper(); + + commit_list_wrapper(commit_list_wrapper&&) noexcept = default; + commit_list_wrapper& operator=(commit_list_wrapper&&) noexcept = default; + + size_t size() const; + +private: + + std::vector m_commit_list; + +}; diff --git a/src/wrapper/refs_wrapper.cpp b/src/wrapper/refs_wrapper.cpp index 571ca52..214274c 100644 --- a/src/wrapper/refs_wrapper.cpp +++ b/src/wrapper/refs_wrapper.cpp @@ -1,4 +1,7 @@ #include "../utils/git_exception.hpp" +#include "object_wrapper.hpp" +#include +#include #include "../wrapper/refs_wrapper.hpp" reference_wrapper::reference_wrapper(git_reference* ref) @@ -21,3 +24,15 @@ bool reference_wrapper::is_remote() const { return git_reference_is_remote(*this); } + +const git_oid* reference_wrapper::target() const +{ + return git_reference_target(p_resource); +} + +reference_wrapper reference_wrapper::new_ref(const git_oid* target_oid) +{ + git_reference* new_ref; + throw_if_error(git_reference_set_target(&new_ref, p_resource, target_oid, NULL)); + return reference_wrapper(new_ref); +} diff --git a/src/wrapper/refs_wrapper.hpp b/src/wrapper/refs_wrapper.hpp index e7509c2..0fe237e 100644 --- a/src/wrapper/refs_wrapper.hpp +++ b/src/wrapper/refs_wrapper.hpp @@ -5,7 +5,9 @@ #include +#include "../utils/git_exception.hpp" #include "../wrapper/wrapper_base.hpp" +#include "../wrapper/object_wrapper.hpp" class reference_wrapper : public wrapper_base { @@ -20,6 +22,8 @@ class reference_wrapper : public wrapper_base std::string short_name() const; bool is_remote() const; + const git_oid* target() const; + reference_wrapper new_ref(const git_oid* target_oid); template W peel() const; diff --git a/src/wrapper/repository_wrapper.cpp b/src/wrapper/repository_wrapper.cpp index 6eb00f4..7e300af 100644 --- a/src/wrapper/repository_wrapper.cpp +++ b/src/wrapper/repository_wrapper.cpp @@ -1,6 +1,7 @@ #include "../utils/git_exception.hpp" #include "../wrapper/index_wrapper.hpp" #include "../wrapper/object_wrapper.hpp" +#include "../wrapper/commit_wrapper.hpp" #include "../wrapper/repository_wrapper.hpp" repository_wrapper::~repository_wrapper() @@ -35,6 +36,8 @@ git_repository_state_t repository_wrapper::state() const return git_repository_state_t(git_repository_state(*this)); } +// References + reference_wrapper repository_wrapper::head() const { git_reference* ref; @@ -56,12 +59,16 @@ std::optional repository_wrapper::find_reference_dwim(std::st return rc == 0 ? std::make_optional(reference_wrapper(ref)) : std::nullopt; } +// Index + index_wrapper repository_wrapper::make_index() { index_wrapper index = index_wrapper::init(*this); return index; } +// Branches + branch_wrapper repository_wrapper::create_branch(std::string_view name, bool force) { return create_branch(name, find_commit(), force); @@ -95,6 +102,8 @@ branch_iterator repository_wrapper::iterate_branches(git_branch_t type) const return branch_iterator(iter); } +// Commits + commit_wrapper repository_wrapper::find_commit(std::string_view ref_name) const { git_oid oid_parent_commit; @@ -110,20 +119,36 @@ commit_wrapper repository_wrapper::find_commit(const git_oid& id) const } void repository_wrapper::create_commit(const signature_wrapper::author_committer_signatures& author_committer_signatures, - const std::string& message) + const std::string& message, const std::optional& parents_list) { const char* message_encoding = "UTF-8"; git_oid commit_id; std::string update_ref = "HEAD"; - auto parent = revparse_single(update_ref); - std::size_t parent_count = 0; - const git_commit* parents[1] = {nullptr}; - if (parent) + const git_commit* placeholder[1] = {nullptr}; + + auto [parents, parents_count] = [&]() -> std::pair { - parent_count = 1; - parents[0] = *parent; - } + if (parents_list) + { + // TODO: write a "as_const" function to replace the following + auto pl_size = parents_list.value().size(); + git_commit** pl_value = parents_list.value(); + auto pl_value_const = const_cast(pl_value); + return {pl_value_const, pl_size}; + } + else + { + auto parent = revparse_single(update_ref); + size_t parents_count = 0; + if (parent) + { + parents_count = 1; + placeholder[0] = *parent; + } + return {placeholder, parents_count}; + } + }(); git_tree* tree; index_wrapper index = this->make_index(); @@ -133,11 +158,32 @@ void repository_wrapper::create_commit(const signature_wrapper::author_committer throw_if_error(git_tree_lookup(&tree, *this, &tree_id)); throw_if_error(git_commit_create(&commit_id, *this, update_ref.c_str(), author_committer_signatures.first, author_committer_signatures.second, - message_encoding, message.c_str(), tree, parent_count, parents)); + message_encoding, message.c_str(), tree, parents_count, parents)); git_tree_free(tree); } +std::optional repository_wrapper::resolve_local_ref +( + const std::string& target_name +) const +{ + if (auto ref = this->find_reference_dwim(target_name)) + { + return this->find_annotated_commit(*ref); + } + else if (auto obj = this->revparse_single(target_name)) + { + return this->find_annotated_commit(obj->oid()); + } + else + { + return std::nullopt; + } +} + +// Annotated commits + annotated_commit_wrapper repository_wrapper::find_annotated_commit(const git_oid& id) const { git_annotated_commit* commit; @@ -145,6 +191,8 @@ annotated_commit_wrapper repository_wrapper::find_annotated_commit(const git_oid return annotated_commit_wrapper(commit); } +// Objects + std::optional repository_wrapper::revparse_single(std::string_view spec) const { git_object* obj; @@ -152,6 +200,15 @@ std::optional repository_wrapper::revparse_single(std::string_vi return rc == 0 ? std::make_optional(object_wrapper(obj)) : std::nullopt; } +object_wrapper repository_wrapper::find_object(const git_oid* id, git_object_t type) +{ + git_object* object; + git_object_lookup(&object, *this, id, type); + return object_wrapper(object); +} + +// Head manipulations + void repository_wrapper::set_head(std::string_view ref_name) { throw_if_error(git_repository_set_head(*this, ref_name.data())); @@ -168,3 +225,10 @@ void repository_wrapper::reset(const object_wrapper& target, git_reset_t reset_t throw_if_error(git_reset(*this, target, reset_type, &checkout_options)); } + +// Trees + +void repository_wrapper::checkout_tree(const object_wrapper& target, const git_checkout_options* opts) +{ + throw_if_error(git_checkout_tree(*this, target, opts)); +} diff --git a/src/wrapper/repository_wrapper.hpp b/src/wrapper/repository_wrapper.hpp index 7258724..ba7bda8 100644 --- a/src/wrapper/repository_wrapper.hpp +++ b/src/wrapper/repository_wrapper.hpp @@ -51,7 +51,8 @@ class repository_wrapper : public wrapper_base // Commits commit_wrapper find_commit(std::string_view ref_name = "HEAD") const; commit_wrapper find_commit(const git_oid& id) const; - void create_commit(const signature_wrapper::author_committer_signatures&, const std::string&); + void create_commit(const signature_wrapper::author_committer_signatures&, const std::string&, const std::optional& parents_list); + std::optional resolve_local_ref(const std::string& target_name) const; // Annotated commits annotated_commit_wrapper find_annotated_commit(const git_oid& id) const; @@ -61,12 +62,16 @@ class repository_wrapper : public wrapper_base // Objects std::optional revparse_single(std::string_view spec) const; + object_wrapper find_object(const git_oid* id, git_object_t type); // Head manipulations void set_head(std::string_view ref_name); void set_head_detached(const annotated_commit_wrapper& commit); void reset(const object_wrapper& target, git_reset_t reset_type, const git_checkout_options& checkout_options); + // Trees + void checkout_tree(const object_wrapper& target, const git_checkout_options* opts); + private: repository_wrapper() = default; diff --git a/src/wrapper/signature_wrapper.cpp b/src/wrapper/signature_wrapper.cpp index d16eaf9..7d0eaa0 100644 --- a/src/wrapper/signature_wrapper.cpp +++ b/src/wrapper/signature_wrapper.cpp @@ -1,6 +1,7 @@ #include "../wrapper/repository_wrapper.hpp" #include "../wrapper/signature_wrapper.hpp" #include "../utils/git_exception.hpp" +#include signature_wrapper::~signature_wrapper() { @@ -49,3 +50,22 @@ signature_wrapper signature_wrapper::get_commit_committer(const commit_wrapper& committer.m_ownership = false; return committer; } + +signature_wrapper signature_wrapper::signature_now(std::string name, std::string email) +{ + signature_wrapper sw; + git_signature* signature; + throw_if_error(git_signature_now(&signature, name.c_str(), email.c_str())); + sw.p_resource = signature; + sw.m_ownership = true; + return sw; +} + +signature_wrapper::author_committer_signatures signature_wrapper::signature_now(std::string author_name, std::string author_email, std::string committer_name, std::string committer_email) +{ + signature_wrapper author_sig = signature_now(author_name.c_str(), author_email.c_str()); + signature_wrapper cmt_sig = signature_now(committer_name.c_str(), committer_email.c_str()); + // Deep copy of "when", which contains only copiable values, not pointers + cmt_sig.p_resource->when = author_sig.p_resource->when; + return std::pair(std::move(author_sig), std::move(cmt_sig)); +} diff --git a/src/wrapper/signature_wrapper.hpp b/src/wrapper/signature_wrapper.hpp index 2ebc861..e0496e6 100644 --- a/src/wrapper/signature_wrapper.hpp +++ b/src/wrapper/signature_wrapper.hpp @@ -13,12 +13,13 @@ class repository_wrapper; class signature_wrapper : public wrapper_base { public: + using author_committer_signatures = std::pair; ~signature_wrapper(); - signature_wrapper(signature_wrapper&&) = default; - signature_wrapper& operator=(signature_wrapper&&) = default; + signature_wrapper(signature_wrapper&&) noexcept = default; + signature_wrapper& operator=(signature_wrapper&&) noexcept = default; std::string_view name() const; std::string_view email() const; @@ -27,6 +28,8 @@ class signature_wrapper : public wrapper_base static author_committer_signatures get_default_signature_from_env(repository_wrapper&); static signature_wrapper get_commit_author(const commit_wrapper&); static signature_wrapper get_commit_committer(const commit_wrapper&); + static signature_wrapper signature_now(std::string name, std::string email); + static author_committer_signatures signature_now(std::string author_name, std::string autor_email, std::string committer_name, std::string committer_email); private: diff --git a/test/conftest.py b/test/conftest.py index 576b5a3..aaf2ff5 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -4,7 +4,7 @@ import subprocess -# Fixture to run test in current tmp_path +# Fixture to run test in current tmp_path @pytest.fixture def run_in_tmp_path(tmp_path): original_cwd = os.getcwd() @@ -12,15 +12,18 @@ def run_in_tmp_path(tmp_path): yield os.chdir(original_cwd) -@pytest.fixture(scope='session') + +@pytest.fixture(scope="session") def git2cpp_path(): - return Path(__file__).parent.parent / 'build' / 'git2cpp' + return Path(__file__).parent.parent / "build" / "git2cpp" + @pytest.fixture def xtl_clone(git2cpp_path, tmp_path, run_in_tmp_path): - url = 'https://github.com/xtensor-stack/xtl.git' - clone_cmd = [git2cpp_path, 'clone', url] - subprocess.run(clone_cmd, capture_output=True, cwd = tmp_path, text=True) + url = "https://github.com/xtensor-stack/xtl.git" + clone_cmd = [git2cpp_path, "clone", url] + subprocess.run(clone_cmd, capture_output=True, cwd=tmp_path, text=True) + @pytest.fixture def git_config(monkeypatch): diff --git a/test/test_merge.py b/test/test_merge.py new file mode 100644 index 0000000..85cd6ed --- /dev/null +++ b/test/test_merge.py @@ -0,0 +1,47 @@ +import subprocess +import time + +import pytest + + +# TODO: Have a different "person" for the commit and for the merge +def test_merge_fast_forward(xtl_clone, git_config, git2cpp_path, tmp_path, monkeypatch): + assert (tmp_path / "xtl").exists() + xtl_path = tmp_path / "xtl" + + checkout_cmd = [git2cpp_path, "checkout", "-b", "foregone"] + p_checkout = subprocess.run( + checkout_cmd, capture_output=True, cwd=xtl_path, text=True + ) + assert p_checkout.returncode == 0 + + p = xtl_path / "mook_file.txt" + p.write_text("blablabla") + + add_cmd = [git2cpp_path, "add", "mook_file.txt"] + p_add = subprocess.run(add_cmd, capture_output=True, cwd=xtl_path, text=True) + assert p_add.returncode == 0 + + commit_cmd = [git2cpp_path, "commit", "-m", "test commit"] + p_commit = subprocess.run(commit_cmd, capture_output=True, cwd=xtl_path, text=True) + assert p_commit.returncode == 0 + + checkout_cmd_2 = [git2cpp_path, "checkout", "master"] + p_checkout_2 = subprocess.run( + checkout_cmd_2, capture_output=True, cwd=xtl_path, text=True + ) + assert p_checkout_2.returncode == 0 + + time.sleep(2) + + merge_cmd = [git2cpp_path, "merge", "foregone"] + p_merge = subprocess.run(merge_cmd, capture_output=True, cwd=xtl_path, text=True) + assert p_merge.returncode == 0 + assert "Fast-forward" in p_merge.stdout + + log_cmd = [git2cpp_path, "log", "--format=full", "--max-count", "1"] + p_log = subprocess.run(log_cmd, capture_output=True, cwd=xtl_path, text=True) + assert p_log.returncode == 0 + assert "Author: Jane Doe" in p_log.stdout + # assert "Commit: John Doe" in p_log.stdout + assert (xtl_path / "mook_file.txt").exists() From 7d7ec312f1c403e07d2244f8699859e35f8df527 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Tue, 28 Oct 2025 15:13:24 +0100 Subject: [PATCH 2/4] comment unused code --- src/subcommand/merge_subcommand.cpp | 26 +++++++++++++------------- test/test_merge.py | 1 + 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/subcommand/merge_subcommand.cpp b/src/subcommand/merge_subcommand.cpp index 5e6ce30..cc213ba 100644 --- a/src/subcommand/merge_subcommand.cpp +++ b/src/subcommand/merge_subcommand.cpp @@ -151,21 +151,21 @@ void merge_subcommand::run() assert(num_commits_to_merge == 1); perform_fastforward(repo, target_oid, (analysis & GIT_MERGE_ANALYSIS_UNBORN)); } - else if (analysis & GIT_MERGE_ANALYSIS_NORMAL) - { - git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + // else if (analysis & GIT_MERGE_ANALYSIS_NORMAL) + // { + // git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + // git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - merge_opts.flags = 0; - merge_opts.file_flags = GIT_MERGE_FILE_STYLE_DIFF3; + // merge_opts.flags = 0; + // merge_opts.file_flags = GIT_MERGE_FILE_STYLE_DIFF3; - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE|GIT_CHECKOUT_ALLOW_CONFLICTS; + // checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE|GIT_CHECKOUT_ALLOW_CONFLICTS; - if (preference & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY) - { - std::cout << "Fast-forward is preferred, but only a merge is possible\n" << std::endl; - // how to break ? - } + // if (preference & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY) + // { + // std::cout << "Fast-forward is preferred, but only a merge is possible\n" << std::endl; + // // how to break ? + // } // git_merge(repo, // (const git_annotated_commit **)opts.annotated, opts.annotated_count, @@ -179,4 +179,4 @@ void merge_subcommand::run() // create_merge_commit(repo, index, &opts); // printf("Merge made\n"); // } -} +// } diff --git a/test/test_merge.py b/test/test_merge.py index 85cd6ed..395ad2b 100644 --- a/test/test_merge.py +++ b/test/test_merge.py @@ -5,6 +5,7 @@ # TODO: Have a different "person" for the commit and for the merge +# TODO: Test "unborn" case, but how ? def test_merge_fast_forward(xtl_clone, git_config, git2cpp_path, tmp_path, monkeypatch): assert (tmp_path / "xtl").exists() xtl_path = tmp_path / "xtl" From 33bebf06559b1b3505246e6c42c9102948c69ac7 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Tue, 28 Oct 2025 15:27:59 +0100 Subject: [PATCH 3/4] clean code --- src/subcommand/merge_subcommand.cpp | 84 +---------------------------- test/test_merge.py | 2 - 2 files changed, 1 insertion(+), 85 deletions(-) diff --git a/src/subcommand/merge_subcommand.cpp b/src/subcommand/merge_subcommand.cpp index cc213ba..2753b9d 100644 --- a/src/subcommand/merge_subcommand.cpp +++ b/src/subcommand/merge_subcommand.cpp @@ -54,60 +54,6 @@ void perform_fastforward(repository_wrapper& repo, const git_oid* target_oid, in auto new_target_ref = target_ref.new_ref(target_oid); } -// static void create_merge_commit(repository_wrapper repo, index_wrapper index, std::vector m_branches_to_merge, -// std::vector commits_to_merge) -// { -// auto head_ref = repo.head(); -// auto merge_ref = repo.find_reference_dwim(m_branches_to_merge.front()); -// // if (ref) -// // { -// // auto merge_ref = std::move(ref).value(); -// // } -// auto merge_commit = repo.resolve_local_ref(m_branches_to_merge.front()).value(); - -// size_t annotated_count = commits_to_merge.size(); -// std::vector parents_list; -// parents_list.reserve(annotated_count + 1); -// parents_list.push_back(std::move(head_ref.peel())); -// for (size_t i=0; ishort_name(); -// } -// else -// { -// msg_target = git_oid_tostr_s(&(merge_commit.oid())); -// } - -// std::string msg; -// msg = "Merge "; -// if (merge_ref) -// { -// msg.append("branch "); -// } -// else -// { -// msg.append("commit "); -// } -// msg.append(msg_target); -// std::cout << msg << std::endl; - -// repo.create_commit(author_committer_sign_now, msg, std::optional(std::move(parents))); -// } - void merge_subcommand::run() { auto directory = get_current_git_path(); @@ -151,32 +97,4 @@ void merge_subcommand::run() assert(num_commits_to_merge == 1); perform_fastforward(repo, target_oid, (analysis & GIT_MERGE_ANALYSIS_UNBORN)); } - // else if (analysis & GIT_MERGE_ANALYSIS_NORMAL) - // { - // git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; - // git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - // merge_opts.flags = 0; - // merge_opts.file_flags = GIT_MERGE_FILE_STYLE_DIFF3; - - // checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE|GIT_CHECKOUT_ALLOW_CONFLICTS; - - // if (preference & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY) - // { - // std::cout << "Fast-forward is preferred, but only a merge is possible\n" << std::endl; - // // how to break ? - // } - - // git_merge(repo, - // (const git_annotated_commit **)opts.annotated, opts.annotated_count, - // &merge_opts, &checkout_opts); - } - - // if (git_index_has_conflicts(index)) { - // /* Handle conflicts */ - // output_conflicts(index); - // } else if (!opts.no_commit) { - // create_merge_commit(repo, index, &opts); - // printf("Merge made\n"); - // } -// } +} diff --git a/test/test_merge.py b/test/test_merge.py index 395ad2b..95bcfc2 100644 --- a/test/test_merge.py +++ b/test/test_merge.py @@ -33,8 +33,6 @@ def test_merge_fast_forward(xtl_clone, git_config, git2cpp_path, tmp_path, monke ) assert p_checkout_2.returncode == 0 - time.sleep(2) - merge_cmd = [git2cpp_path, "merge", "foregone"] p_merge = subprocess.run(merge_cmd, capture_output=True, cwd=xtl_path, text=True) assert p_merge.returncode == 0 From d4f8309bbde10a892779a3c54bdc7b66fb8dfd70 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Wed, 29 Oct 2025 16:58:38 +0100 Subject: [PATCH 4/4] address review comments --- src/subcommand/checkout_subcommand.cpp | 20 ----------- src/subcommand/checkout_subcommand.hpp | 6 ---- src/subcommand/merge_subcommand.cpp | 20 +++++------ src/subcommand/merge_subcommand.hpp | 4 +++ src/wrapper/annotated_commit_wrapper.cpp | 36 ------------------- src/wrapper/annotated_commit_wrapper.hpp | 24 +------------ src/wrapper/commit_wrapper.cpp | 20 ----------- src/wrapper/commit_wrapper.hpp | 21 +---------- src/wrapper/refs_wrapper.cpp | 4 +-- src/wrapper/refs_wrapper.hpp | 2 +- src/wrapper/repository_wrapper.cpp | 14 ++++---- src/wrapper/repository_wrapper.hpp | 8 ++--- src/wrapper/signature_wrapper.cpp | 11 +++--- src/wrapper/signature_wrapper.hpp | 10 ++++-- src/wrapper/wrapper_base.hpp | 44 ++++++++++++++++++++++++ 15 files changed, 88 insertions(+), 156 deletions(-) diff --git a/src/subcommand/checkout_subcommand.cpp b/src/subcommand/checkout_subcommand.cpp index 6ae3e42..d0103d7 100644 --- a/src/subcommand/checkout_subcommand.cpp +++ b/src/subcommand/checkout_subcommand.cpp @@ -56,26 +56,6 @@ void checkout_subcommand::run() } } -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, diff --git a/src/subcommand/checkout_subcommand.hpp b/src/subcommand/checkout_subcommand.hpp index 533b46a..2aab79e 100644 --- a/src/subcommand/checkout_subcommand.hpp +++ b/src/subcommand/checkout_subcommand.hpp @@ -17,12 +17,6 @@ class checkout_subcommand private: - std::optional resolve_local_ref - ( - const repository_wrapper& repo, - const std::string& target_name - ); - annotated_commit_wrapper create_local_branch ( repository_wrapper& repo, diff --git a/src/subcommand/merge_subcommand.cpp b/src/subcommand/merge_subcommand.cpp index 2753b9d..a9e62be 100644 --- a/src/subcommand/merge_subcommand.cpp +++ b/src/subcommand/merge_subcommand.cpp @@ -2,7 +2,7 @@ #include #include "merge_subcommand.hpp" -#include "../wrapper/repository_wrapper.hpp" +// #include "../wrapper/repository_wrapper.hpp" merge_subcommand::merge_subcommand(const libgit2_object&, CLI::App& app) @@ -14,7 +14,7 @@ merge_subcommand::merge_subcommand(const libgit2_object&, CLI::App& app) sub->callback([this]() { this->run(); }); } -annotated_commit_list_wrapper resolve_heads(const repository_wrapper& repo, std::vector m_branches_to_merge) +annotated_commit_list_wrapper merge_subcommand::resolve_heads(const repository_wrapper& repo) { std::vector commits_to_merge; commits_to_merge.reserve(m_branches_to_merge.size()); @@ -30,7 +30,7 @@ annotated_commit_list_wrapper resolve_heads(const repository_wrapper& repo, std: return annotated_commit_list_wrapper(std::move(commits_to_merge)); } -void perform_fastforward(repository_wrapper& repo, const git_oid* target_oid, int is_unborn) +void perform_fastforward(repository_wrapper& repo, const git_oid target_oid, int is_unborn) { const git_checkout_options ff_checkout_options = GIT_CHECKOUT_OPTIONS_INIT; @@ -45,13 +45,13 @@ void perform_fastforward(repository_wrapper& repo, const git_oid* target_oid, in return repo->find_reference("HEAD"); } }; - auto target_ref = lambda_get_target_ref(&repo, is_unborn); + reference_wrapper target_ref = lambda_get_target_ref(&repo, is_unborn); - auto target = repo.find_object(target_oid, GIT_OBJECT_COMMIT); + object_wrapper target = repo.find_object(target_oid, GIT_OBJECT_COMMIT); - repo.checkout_tree(target, &ff_checkout_options); + repo.checkout_tree(target, ff_checkout_options); - auto new_target_ref = target_ref.new_ref(target_oid); + target_ref.write_new_ref(target_oid); } void merge_subcommand::run() @@ -68,7 +68,7 @@ void merge_subcommand::run() git_merge_analysis_t analysis; git_merge_preference_t preference; - annotated_commit_list_wrapper commits_to_merge = resolve_heads(repo, m_branches_to_merge); + annotated_commit_list_wrapper commits_to_merge = resolve_heads(repo); size_t num_commits_to_merge = commits_to_merge.size(); git_annotated_commit** c_commits_to_merge = commits_to_merge; auto commits_to_merge_const = const_cast(c_commits_to_merge); @@ -83,7 +83,6 @@ void merge_subcommand::run() (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD && !(preference & GIT_MERGE_PREFERENCE_NO_FASTFORWARD))) { - const git_oid* target_oid; if (analysis & GIT_MERGE_ANALYSIS_UNBORN) { std::cout << "Unborn" << std::endl; @@ -93,7 +92,8 @@ void merge_subcommand::run() std::cout << "Fast-forward" << std::endl; } const annotated_commit_wrapper& commit = commits_to_merge.front(); - target_oid = &commit.oid(); + const git_oid target_oid = commit.oid(); + // Since this is a fast-forward, there can be only one merge head. assert(num_commits_to_merge == 1); perform_fastforward(repo, target_oid, (analysis & GIT_MERGE_ANALYSIS_UNBORN)); } diff --git a/src/subcommand/merge_subcommand.hpp b/src/subcommand/merge_subcommand.hpp index 306d65d..3d73f47 100644 --- a/src/subcommand/merge_subcommand.hpp +++ b/src/subcommand/merge_subcommand.hpp @@ -3,6 +3,7 @@ #include #include "../utils/common.hpp" +#include "../wrapper/repository_wrapper.hpp" class merge_subcommand { @@ -12,5 +13,8 @@ class merge_subcommand void run(); private: + + annotated_commit_list_wrapper resolve_heads(const repository_wrapper& repo); + std::vector m_branches_to_merge; }; diff --git a/src/wrapper/annotated_commit_wrapper.cpp b/src/wrapper/annotated_commit_wrapper.cpp index a3f89e2..da38620 100644 --- a/src/wrapper/annotated_commit_wrapper.cpp +++ b/src/wrapper/annotated_commit_wrapper.cpp @@ -1,5 +1,4 @@ #include "../wrapper/annotated_commit_wrapper.hpp" -#include annotated_commit_wrapper::annotated_commit_wrapper(git_annotated_commit* commit) : base_type(commit) @@ -22,38 +21,3 @@ std::string_view annotated_commit_wrapper::reference_name() const const char* res = git_annotated_commit_ref(*this); return res ? res : std::string_view{}; } - -annotated_commit_list_wrapper::annotated_commit_list_wrapper(std::vector annotated_commit_list) - : m_annotated_commit_list(std::move(annotated_commit_list)) -{ - p_resource = new git_annotated_commit*[m_annotated_commit_list.size()]; - for (size_t i=0; i -#include #include @@ -28,25 +27,4 @@ class annotated_commit_wrapper : public wrapper_base friend class repository_wrapper; }; -class annotated_commit_list_wrapper : public wrapper_base -{ -public: - - using base_type = wrapper_base; - - explicit annotated_commit_list_wrapper(std::vector annotated_commit_list); - - ~annotated_commit_list_wrapper(); - - annotated_commit_list_wrapper(annotated_commit_list_wrapper&&) noexcept = default; - annotated_commit_list_wrapper& operator=(annotated_commit_list_wrapper&&) noexcept = default; - - size_t size() const; - annotated_commit_wrapper front(); - void print() const; - -private: - - std::vector m_annotated_commit_list; - -}; +using annotated_commit_list_wrapper = list_wrapper; diff --git a/src/wrapper/commit_wrapper.cpp b/src/wrapper/commit_wrapper.cpp index f079e5b..a5996fc 100644 --- a/src/wrapper/commit_wrapper.cpp +++ b/src/wrapper/commit_wrapper.cpp @@ -26,23 +26,3 @@ std::string commit_wrapper::commit_oid_tostr() const char buf[GIT_OID_SHA1_HEXSIZE + 1]; return git_oid_tostr(buf, sizeof(buf), &this->oid()); } - -commit_list_wrapper::commit_list_wrapper(std::vector commit_list) -{ - git_commit** p_resource = new git_commit*[m_commit_list.size()]; - for (size_t i=0; i friend class reference_wrapper; }; -class commit_list_wrapper : public wrapper_base -{ -public: - - using base_type = wrapper_base; - - explicit commit_list_wrapper(std::vector commit_list); - - ~commit_list_wrapper(); - - commit_list_wrapper(commit_list_wrapper&&) noexcept = default; - commit_list_wrapper& operator=(commit_list_wrapper&&) noexcept = default; - - size_t size() const; - -private: - - std::vector m_commit_list; - -}; +using commit_list_wrapper = list_wrapper; diff --git a/src/wrapper/refs_wrapper.cpp b/src/wrapper/refs_wrapper.cpp index 214274c..4af5906 100644 --- a/src/wrapper/refs_wrapper.cpp +++ b/src/wrapper/refs_wrapper.cpp @@ -30,9 +30,9 @@ const git_oid* reference_wrapper::target() const return git_reference_target(p_resource); } -reference_wrapper reference_wrapper::new_ref(const git_oid* target_oid) +reference_wrapper reference_wrapper::write_new_ref(const git_oid target_oid) { git_reference* new_ref; - throw_if_error(git_reference_set_target(&new_ref, p_resource, target_oid, NULL)); + throw_if_error(git_reference_set_target(&new_ref, p_resource, &target_oid, NULL)); return reference_wrapper(new_ref); } diff --git a/src/wrapper/refs_wrapper.hpp b/src/wrapper/refs_wrapper.hpp index 0fe237e..d7645ca 100644 --- a/src/wrapper/refs_wrapper.hpp +++ b/src/wrapper/refs_wrapper.hpp @@ -23,7 +23,7 @@ class reference_wrapper : public wrapper_base std::string short_name() const; bool is_remote() const; const git_oid* target() const; - reference_wrapper new_ref(const git_oid* target_oid); + reference_wrapper write_new_ref(const git_oid target_oid); template W peel() const; diff --git a/src/wrapper/repository_wrapper.cpp b/src/wrapper/repository_wrapper.cpp index 7e300af..d099382 100644 --- a/src/wrapper/repository_wrapper.cpp +++ b/src/wrapper/repository_wrapper.cpp @@ -119,7 +119,7 @@ commit_wrapper repository_wrapper::find_commit(const git_oid& id) const } void repository_wrapper::create_commit(const signature_wrapper::author_committer_signatures& author_committer_signatures, - const std::string& message, const std::optional& parents_list) + const std::string_view message, const std::optional& parents_list) { const char* message_encoding = "UTF-8"; git_oid commit_id; @@ -158,14 +158,14 @@ void repository_wrapper::create_commit(const signature_wrapper::author_committer throw_if_error(git_tree_lookup(&tree, *this, &tree_id)); throw_if_error(git_commit_create(&commit_id, *this, update_ref.c_str(), author_committer_signatures.first, author_committer_signatures.second, - message_encoding, message.c_str(), tree, parents_count, parents)); + message_encoding, message.data(), tree, parents_count, parents)); git_tree_free(tree); } std::optional repository_wrapper::resolve_local_ref ( - const std::string& target_name + const std::string_view target_name ) const { if (auto ref = this->find_reference_dwim(target_name)) @@ -200,10 +200,10 @@ std::optional repository_wrapper::revparse_single(std::string_vi return rc == 0 ? std::make_optional(object_wrapper(obj)) : std::nullopt; } -object_wrapper repository_wrapper::find_object(const git_oid* id, git_object_t type) +object_wrapper repository_wrapper::find_object(const git_oid id, git_object_t type) { git_object* object; - git_object_lookup(&object, *this, id, type); + git_object_lookup(&object, *this, &id, type); return object_wrapper(object); } @@ -228,7 +228,7 @@ void repository_wrapper::reset(const object_wrapper& target, git_reset_t reset_t // Trees -void repository_wrapper::checkout_tree(const object_wrapper& target, const git_checkout_options* opts) +void repository_wrapper::checkout_tree(const object_wrapper& target, const git_checkout_options opts) { - throw_if_error(git_checkout_tree(*this, target, opts)); + throw_if_error(git_checkout_tree(*this, target, &opts)); } diff --git a/src/wrapper/repository_wrapper.hpp b/src/wrapper/repository_wrapper.hpp index ba7bda8..78212cc 100644 --- a/src/wrapper/repository_wrapper.hpp +++ b/src/wrapper/repository_wrapper.hpp @@ -51,8 +51,8 @@ class repository_wrapper : public wrapper_base // Commits commit_wrapper find_commit(std::string_view ref_name = "HEAD") const; commit_wrapper find_commit(const git_oid& id) const; - void create_commit(const signature_wrapper::author_committer_signatures&, const std::string&, const std::optional& parents_list); - std::optional resolve_local_ref(const std::string& target_name) const; + void create_commit(const signature_wrapper::author_committer_signatures&, const std::string_view, const std::optional& parents_list); + std::optional resolve_local_ref(const std::string_view target_name) const; // Annotated commits annotated_commit_wrapper find_annotated_commit(const git_oid& id) const; @@ -62,7 +62,7 @@ class repository_wrapper : public wrapper_base // Objects std::optional revparse_single(std::string_view spec) const; - object_wrapper find_object(const git_oid* id, git_object_t type); + object_wrapper find_object(const git_oid id, git_object_t type); // Head manipulations void set_head(std::string_view ref_name); @@ -70,7 +70,7 @@ class repository_wrapper : public wrapper_base void reset(const object_wrapper& target, git_reset_t reset_type, const git_checkout_options& checkout_options); // Trees - void checkout_tree(const object_wrapper& target, const git_checkout_options* opts); + void checkout_tree(const object_wrapper& target, const git_checkout_options opts); private: diff --git a/src/wrapper/signature_wrapper.cpp b/src/wrapper/signature_wrapper.cpp index 7d0eaa0..b6148d9 100644 --- a/src/wrapper/signature_wrapper.cpp +++ b/src/wrapper/signature_wrapper.cpp @@ -51,20 +51,21 @@ signature_wrapper signature_wrapper::get_commit_committer(const commit_wrapper& return committer; } -signature_wrapper signature_wrapper::signature_now(std::string name, std::string email) +signature_wrapper signature_wrapper::signature_now(std::string_view name, std::string_view email) { signature_wrapper sw; git_signature* signature; - throw_if_error(git_signature_now(&signature, name.c_str(), email.c_str())); + throw_if_error(git_signature_now(&signature, name.data(), email.data())); sw.p_resource = signature; sw.m_ownership = true; return sw; } -signature_wrapper::author_committer_signatures signature_wrapper::signature_now(std::string author_name, std::string author_email, std::string committer_name, std::string committer_email) +signature_wrapper::author_committer_signatures signature_wrapper::signature_now( + std::string_view author_name, std::string_view author_email, std::string_view committer_name, std::string_view committer_email) { - signature_wrapper author_sig = signature_now(author_name.c_str(), author_email.c_str()); - signature_wrapper cmt_sig = signature_now(committer_name.c_str(), committer_email.c_str()); + signature_wrapper author_sig = signature_now(author_name.data(), author_email.data()); + signature_wrapper cmt_sig = signature_now(committer_name.data(), committer_email.data()); // Deep copy of "when", which contains only copiable values, not pointers cmt_sig.p_resource->when = author_sig.p_resource->when; return std::pair(std::move(author_sig), std::move(cmt_sig)); diff --git a/src/wrapper/signature_wrapper.hpp b/src/wrapper/signature_wrapper.hpp index e0496e6..68d9caa 100644 --- a/src/wrapper/signature_wrapper.hpp +++ b/src/wrapper/signature_wrapper.hpp @@ -28,8 +28,14 @@ class signature_wrapper : public wrapper_base static author_committer_signatures get_default_signature_from_env(repository_wrapper&); static signature_wrapper get_commit_author(const commit_wrapper&); static signature_wrapper get_commit_committer(const commit_wrapper&); - static signature_wrapper signature_now(std::string name, std::string email); - static author_committer_signatures signature_now(std::string author_name, std::string autor_email, std::string committer_name, std::string committer_email); + static signature_wrapper signature_now(std::string_view name, std::string_view email); + static author_committer_signatures signature_now + ( + std::string_view author_name, + std::string_view author_email, + std::string_view committer_name, + std::string_view committer_email + ); private: diff --git a/src/wrapper/wrapper_base.hpp b/src/wrapper/wrapper_base.hpp index 08a5662..16e5dd2 100644 --- a/src/wrapper/wrapper_base.hpp +++ b/src/wrapper/wrapper_base.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include template class wrapper_base @@ -38,3 +39,46 @@ class wrapper_base resource_type* p_resource = nullptr; }; + +template +class list_wrapper : public wrapper_base +{ +public: + + using base_type = wrapper_base; + + explicit list_wrapper(std::vector list) + : m_list(std::move(list)) + { + this->p_resource = new base_type::resource_type[m_list.size()]; + for (size_t i=0; i< m_list.size(); ++i) + { + this->p_resource[i] = m_list[i]; + } + } + + ~list_wrapper() + { + delete[] this->p_resource; + this->p_resource = nullptr; + } + + list_wrapper(list_wrapper&&) noexcept = default; + list_wrapper& operator=(list_wrapper&&) noexcept = default; + + size_t size() const + { + return m_list.size(); + } + + T front() + { + // TODO: rework wrapper so they can have references + // on libgit2 object without taking ownership + return T(std::move(m_list.front())); + } + +private: + + std::vector m_list; +};