Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <CLI/CLI.hpp>
#include <cmath>
#include <git2.h> // For version number only
#include <iostream>

Expand All @@ -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"

Expand All @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/checkout_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/commit_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
100 changes: 100 additions & 0 deletions src/subcommand/merge_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#include <cassert>
#include <git2/types.h>

#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("<branch>", 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<std::string> m_branches_to_merge)
{
std::vector<annotated_commit_wrapper> commits_to_merge;
commits_to_merge.reserve(m_branches_to_merge.size());

for (const auto branch_name:m_branches_to_merge)
{
std::optional<annotated_commit_wrapper> 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);
}

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 <<std::endl;
}

git_merge_analysis_t analysis;
git_merge_preference_t preference;
annotated_commit_list_wrapper commits_to_merge = resolve_heads(repo, m_branches_to_merge);
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<const git_annotated_commit**>(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));
}
}
16 changes: 16 additions & 0 deletions src/subcommand/merge_subcommand.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include <CLI/CLI.hpp>

#include "../utils/common.hpp"

class merge_subcommand
{
public:

explicit merge_subcommand(const libgit2_object&, CLI::App& app);
void run();

private:
std::vector<std::string> m_branches_to_merge;
};
36 changes: 36 additions & 0 deletions src/wrapper/annotated_commit_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "../wrapper/annotated_commit_wrapper.hpp"
#include <iostream>

annotated_commit_wrapper::annotated_commit_wrapper(git_annotated_commit* commit)
: base_type(commit)
Expand All @@ -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_wrapper> annotated_commit_list)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have multiple whatever_list_wrapper classes there is probably a way we can move the generic vector functionality into a single templated class that we inherit from. But that can be future work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new list_wrapper is really nice, thanks!

: 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<m_annotated_commit_list.size(); ++i)
{
p_resource[i] = m_annotated_commit_list[i];
}
}

annotated_commit_list_wrapper::~annotated_commit_list_wrapper()
{
delete[] p_resource;
p_resource = nullptr;
}

size_t annotated_commit_list_wrapper::size() const
{
return m_annotated_commit_list.size();
}

annotated_commit_wrapper annotated_commit_list_wrapper::front()
{
return annotated_commit_wrapper(std::move(m_annotated_commit_list.front()));
}

void annotated_commit_list_wrapper::print() const
{
std::cout << "aclw - p_resource = " << p_resource <<std::endl;
for (size_t i=0; i<m_annotated_commit_list.size(); ++i)
{
std::cout << "aclw - p_resource[i] = :" << p_resource[i] << std::endl;
}
}
23 changes: 23 additions & 0 deletions src/wrapper/annotated_commit_wrapper.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <string_view>
#include <vector>

#include <git2.h>

Expand All @@ -27,3 +28,25 @@ class annotated_commit_wrapper : public wrapper_base<git_annotated_commit>
friend class repository_wrapper;
};

class annotated_commit_list_wrapper : public wrapper_base<git_annotated_commit*>
{
public:

using base_type = wrapper_base<git_annotated_commit*>;

explicit annotated_commit_list_wrapper(std::vector<annotated_commit_wrapper> 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<annotated_commit_wrapper> m_annotated_commit_list;

};
20 changes: 20 additions & 0 deletions src/wrapper/commit_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_wrapper> commit_list)
{
git_commit** p_resource = new git_commit*[m_commit_list.size()];
for (size_t i=0; i<m_commit_list.size(); ++i)
{
p_resource[i] = m_commit_list[i];
}
}

commit_list_wrapper::~commit_list_wrapper()
{
delete[] p_resource;
p_resource = nullptr;
}

size_t commit_list_wrapper::size() const
{
return m_commit_list.size();
}
24 changes: 23 additions & 1 deletion src/wrapper/commit_wrapper.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#pragma once

#include <git2.h>
#include <vector>
#include <string>

#include "../wrapper/repository_wrapper.hpp"
#include "../wrapper/wrapper_base.hpp"

class commit_wrapper : public wrapper_base<git_commit>
Expand All @@ -28,3 +29,24 @@ class commit_wrapper : public wrapper_base<git_commit>
friend class repository_wrapper;
friend class reference_wrapper;
};

class commit_list_wrapper : public wrapper_base<git_commit*>
{
public:

using base_type = wrapper_base<git_commit*>;

explicit commit_list_wrapper(std::vector<commit_wrapper> 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<commit_wrapper> m_commit_list;

};
15 changes: 15 additions & 0 deletions src/wrapper/refs_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#include "../utils/git_exception.hpp"
#include "object_wrapper.hpp"
#include <git2/refs.h>
#include <git2/types.h>
#include "../wrapper/refs_wrapper.hpp"

reference_wrapper::reference_wrapper(git_reference* ref)
Expand All @@ -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);
}
4 changes: 4 additions & 0 deletions src/wrapper/refs_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

#include <git2.h>

#include "../utils/git_exception.hpp"
#include "../wrapper/wrapper_base.hpp"
#include "../wrapper/object_wrapper.hpp"

class reference_wrapper : public wrapper_base<git_reference>
{
Expand All @@ -20,6 +22,8 @@ class reference_wrapper : public wrapper_base<git_reference>

std::string short_name() const;
bool is_remote() const;
const git_oid* target() const;
reference_wrapper new_ref(const git_oid* target_oid);

template <class W>
W peel() const;
Expand Down
Loading