Skip to content

Commit 14b247a

Browse files
committed
Add merge subcommand
1 parent fe44bbd commit 14b247a

18 files changed

+488
-15
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ set(GIT2CPP_SRC
5454
${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.hpp
5555
${GIT2CPP_SOURCE_DIR}/subcommand/log_subcommand.cpp
5656
${GIT2CPP_SOURCE_DIR}/subcommand/log_subcommand.hpp
57+
${GIT2CPP_SOURCE_DIR}/subcommand/merge_subcommand.cpp
58+
${GIT2CPP_SOURCE_DIR}/subcommand/merge_subcommand.hpp
5759
${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.cpp
5860
${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.hpp
5961
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp

src/main.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <CLI/CLI.hpp>
2+
#include <cmath>
23
#include <git2.h> // For version number only
34
#include <iostream>
45

@@ -11,6 +12,7 @@
1112
#include "subcommand/commit_subcommand.hpp"
1213
#include "subcommand/init_subcommand.hpp"
1314
#include "subcommand/log_subcommand.hpp"
15+
#include "subcommand/merge_subcommand.hpp"
1416
#include "subcommand/reset_subcommand.hpp"
1517
#include "subcommand/status_subcommand.hpp"
1618

@@ -35,6 +37,7 @@ int main(int argc, char** argv)
3537
commit_subcommand commit(lg2_obj, app);
3638
reset_subcommand reset(lg2_obj, app);
3739
log_subcommand log(lg2_obj, app);
40+
merge_subcommand merge(lg2_obj, app);
3841

3942
app.require_subcommand(/* min */ 0, /* max */ 1);
4043

src/subcommand/checkout_subcommand.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ void checkout_subcommand::run()
4343
}
4444
else
4545
{
46-
auto optional_commit = resolve_local_ref(repo, m_branch_name);
46+
auto optional_commit = repo.resolve_local_ref(m_branch_name);
4747
if (!optional_commit)
4848
{
4949
// TODO: handle remote refs

src/subcommand/commit_subcommand.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ void commit_subcommand::run()
3232
}
3333
}
3434

35-
repo.create_commit(author_committer_signatures, m_commit_message);
35+
repo.create_commit(author_committer_signatures, m_commit_message, std::nullopt);
3636
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#include <cassert>
2+
#include <git2/types.h>
3+
4+
#include "merge_subcommand.hpp"
5+
#include "../wrapper/repository_wrapper.hpp"
6+
7+
8+
merge_subcommand::merge_subcommand(const libgit2_object&, CLI::App& app)
9+
{
10+
auto *sub = app.add_subcommand("merge", "Join two or more development histories together");
11+
12+
sub->add_option("to merge", m_branches_to_merge, "to merge");
13+
14+
sub->callback([this]() { this->run(); });
15+
}
16+
17+
annotated_commit_list_wrapper resolve_heads(const repository_wrapper& repo, std::vector<std::string> m_branches_to_merge)
18+
{
19+
std::vector<annotated_commit_wrapper> commits_to_merge;
20+
commits_to_merge.reserve(commits_to_merge.size());
21+
22+
for (const auto branch_name:m_branches_to_merge)
23+
{
24+
std::optional<annotated_commit_wrapper> commit = repo.resolve_local_ref(branch_name);
25+
if (commit.has_value())
26+
{
27+
commits_to_merge.push_back(std::move(commit).value());
28+
}
29+
}
30+
return annotated_commit_list_wrapper(std::move(commits_to_merge));
31+
}
32+
33+
void perform_fastforward(repository_wrapper& repo, const git_oid* target_oid, int is_unborn)
34+
{
35+
const git_checkout_options ff_checkout_options = GIT_CHECKOUT_OPTIONS_INIT;
36+
37+
auto lambda_get_target_ref = [] (auto repo, auto is_unborn)
38+
{
39+
if (!is_unborn)
40+
{
41+
return repo->head();
42+
}
43+
else
44+
{
45+
return repo->find_reference("HEAD");
46+
}
47+
};
48+
auto target_ref = lambda_get_target_ref(&repo, is_unborn);
49+
50+
auto target = repo.find_object(target_oid, GIT_OBJECT_COMMIT);
51+
52+
repo.checkout_tree(target, &ff_checkout_options);
53+
54+
auto new_target_ref = target_ref.new_ref();
55+
}
56+
57+
static void create_merge_commit(repository_wrapper repo, index_wrapper index, std::vector<std::string> m_branches_to_merge,
58+
std::vector<annotated_commit_wrapper> commits_to_merge)
59+
{
60+
auto head_ref = repo.head();
61+
auto merge_ref = repo.find_reference_dwim(m_branches_to_merge.front());
62+
// if (ref)
63+
// {
64+
// auto merge_ref = std::move(ref).value();
65+
// }
66+
auto merge_commit = repo.resolve_local_ref(m_branches_to_merge.front()).value();
67+
68+
size_t annotated_count = commits_to_merge.size();
69+
std::vector<commit_wrapper> parents_list;
70+
parents_list.reserve(annotated_count + 1);
71+
parents_list.push_back(std::move(head_ref.peel<commit_wrapper>()));
72+
for (size_t i=0; i<annotated_count; ++i)
73+
{
74+
parents_list.push_back(repo.find_commit(commits_to_merge[i].oid()));
75+
}
76+
auto parents = commit_list_wrapper(std::move(parents_list));
77+
78+
auto author_committer_sign = signature_wrapper::get_default_signature_from_env(repo);
79+
std::string author_name;
80+
author_name = author_committer_sign.first.name();
81+
std::string author_email;
82+
author_email = author_committer_sign.first.email();
83+
auto author_committer_sign_now = signature_wrapper::signature_now(author_name, author_email, author_name, author_email);
84+
85+
std::string msg_target = NULL;
86+
if (merge_ref)
87+
{
88+
msg_target = merge_ref->short_name();
89+
}
90+
else
91+
{
92+
msg_target = git_oid_tostr_s(&(merge_commit.oid()));
93+
}
94+
95+
std::string msg;
96+
msg = "Merge ";
97+
if (merge_ref)
98+
{
99+
msg.append("branch ");
100+
}
101+
else
102+
{
103+
msg.append("commit ");
104+
}
105+
msg.append(msg_target);
106+
std::cout << msg << std::endl;
107+
108+
repo.create_commit(author_committer_sign_now, msg, std::optional<commit_list_wrapper>(std::move(parents)));
109+
}
110+
111+
void merge_subcommand::run()
112+
{
113+
auto directory = get_current_git_path();
114+
auto bare = false;
115+
auto repo = repository_wrapper::open(directory);
116+
117+
auto state = repo.state();
118+
if (state != GIT_REPOSITORY_STATE_NONE)
119+
{
120+
std::cout << "repository is in unexpected state " << state <<std::endl;
121+
}
122+
123+
git_merge_analysis_t analysis;
124+
git_merge_preference_t preference;
125+
annotated_commit_list_wrapper commits_to_merge = resolve_heads(repo, m_branches_to_merge);
126+
size_t num_commits_to_merge = commits_to_merge.size();
127+
git_annotated_commit** c_commits_to_merge = commits_to_merge;
128+
auto commits_to_merge_const = const_cast<const git_annotated_commit**>(c_commits_to_merge);
129+
130+
git_merge_analysis(&analysis, &preference, repo, commits_to_merge_const, num_commits_to_merge);
131+
132+
if (analysis && GIT_MERGE_ANALYSIS_UP_TO_DATE)
133+
{
134+
std::cout << "Already up-to-date" << std::endl;
135+
}
136+
else if (analysis && GIT_MERGE_ANALYSIS_UNBORN ||
137+
(analysis && GIT_MERGE_ANALYSIS_FASTFORWARD &&
138+
!(preference & GIT_MERGE_PREFERENCE_NO_FASTFORWARD)))
139+
{
140+
const git_oid* target_oid;
141+
if (analysis && GIT_MERGE_ANALYSIS_UNBORN)
142+
{
143+
std::cout << "Unborn" << std::endl;
144+
}
145+
else
146+
{
147+
std::cout << "Fast-forward" << std::endl;
148+
}
149+
const annotated_commit_wrapper& commit = commits_to_merge.front();
150+
target_oid = &commit.oid();
151+
assert(num_commits_to_merge == 1);
152+
perform_fastforward(repo, target_oid, (analysis && GIT_MERGE_ANALYSIS_UNBORN));
153+
}
154+
// else if (analysis & GIT_MERGE_ANALYSIS_NORMAL)
155+
// {
156+
// }
157+
158+
159+
160+
161+
// if (preference && GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY)
162+
// {
163+
// std::cout << " Fast-forward is preferred, but only a merge is possible." << std::endl;
164+
// }
165+
166+
// err = git_merge(repo, ???, )
167+
// }
168+
169+
// if (git_index_has_conflicts(index))
170+
// {
171+
172+
// }
173+
// else
174+
// {
175+
176+
// std::cout << "Merge made." << std::cout;
177+
// }
178+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
3+
#include <CLI/CLI.hpp>
4+
5+
#include "../utils/common.hpp"
6+
7+
class merge_subcommand
8+
{
9+
public:
10+
11+
explicit merge_subcommand(const libgit2_object&, CLI::App& app);
12+
void run();
13+
14+
private:
15+
std::vector<std::string> m_branches_to_merge;
16+
};

src/wrapper/annotated_commit_wrapper.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,28 @@ std::string_view annotated_commit_wrapper::reference_name() const
2121
const char* res = git_annotated_commit_ref(*this);
2222
return res ? res : std::string_view{};
2323
}
24+
25+
annotated_commit_list_wrapper::annotated_commit_list_wrapper(std::vector<annotated_commit_wrapper> annotated_commit_list)
26+
{
27+
git_annotated_commit** p_resource = new git_annotated_commit*[m_annotated_commit_list.size()];
28+
for (size_t i=0; i<m_annotated_commit_list.size(); ++i)
29+
{
30+
p_resource[i] = m_annotated_commit_list[i];
31+
}
32+
}
33+
34+
annotated_commit_list_wrapper::~annotated_commit_list_wrapper()
35+
{
36+
delete[] p_resource;
37+
p_resource = nullptr;
38+
}
39+
40+
size_t annotated_commit_list_wrapper::size() const
41+
{
42+
return m_annotated_commit_list.size();
43+
}
44+
45+
annotated_commit_wrapper annotated_commit_list_wrapper::front()
46+
{
47+
return annotated_commit_wrapper(std::move(m_annotated_commit_list.front()));
48+
}

src/wrapper/annotated_commit_wrapper.hpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <string_view>
4+
#include <vector>
45

56
#include <git2.h>
67

@@ -27,3 +28,24 @@ class annotated_commit_wrapper : public wrapper_base<git_annotated_commit>
2728
friend class repository_wrapper;
2829
};
2930

31+
class annotated_commit_list_wrapper : public wrapper_base<git_annotated_commit*>
32+
{
33+
public:
34+
35+
using base_type = wrapper_base<git_annotated_commit*>;
36+
37+
explicit annotated_commit_list_wrapper(std::vector<annotated_commit_wrapper> annotated_commit_list);
38+
39+
~annotated_commit_list_wrapper();
40+
41+
annotated_commit_list_wrapper(annotated_commit_list_wrapper&&) noexcept = default;
42+
annotated_commit_list_wrapper& operator=(annotated_commit_list_wrapper&&) noexcept = default;
43+
44+
size_t size() const;
45+
annotated_commit_wrapper front();
46+
47+
private:
48+
49+
std::vector<annotated_commit_wrapper> m_annotated_commit_list;
50+
51+
};

src/wrapper/commit_wrapper.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,23 @@ std::string commit_wrapper::commit_oid_tostr() const
2626
char buf[GIT_OID_SHA1_HEXSIZE + 1];
2727
return git_oid_tostr(buf, sizeof(buf), &this->oid());
2828
}
29+
30+
commit_list_wrapper::commit_list_wrapper(std::vector<commit_wrapper> commit_list)
31+
{
32+
git_commit** p_resource = new git_commit*[m_commit_list.size()];
33+
for (size_t i=0; i<m_commit_list.size(); ++i)
34+
{
35+
p_resource[i] = m_commit_list[i];
36+
}
37+
}
38+
39+
commit_list_wrapper::~commit_list_wrapper()
40+
{
41+
delete[] p_resource;
42+
p_resource = nullptr;
43+
}
44+
45+
size_t commit_list_wrapper::size() const
46+
{
47+
return m_commit_list.size();
48+
}

src/wrapper/commit_wrapper.hpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
#pragma once
22

33
#include <git2.h>
4+
#include <vector>
5+
#include <string>
46

5-
#include "../wrapper/repository_wrapper.hpp"
67
#include "../wrapper/wrapper_base.hpp"
78

89
class commit_wrapper : public wrapper_base<git_commit>
@@ -26,4 +27,26 @@ class commit_wrapper : public wrapper_base<git_commit>
2627
commit_wrapper(git_commit* commit);
2728

2829
friend class repository_wrapper;
30+
friend class reference_wrapper;
31+
};
32+
33+
class commit_list_wrapper : public wrapper_base<git_commit*>
34+
{
35+
public:
36+
37+
using base_type = wrapper_base<git_commit*>;
38+
39+
explicit commit_list_wrapper(std::vector<commit_wrapper> commit_list);
40+
41+
~commit_list_wrapper();
42+
43+
commit_list_wrapper(commit_list_wrapper&&) noexcept = default;
44+
commit_list_wrapper& operator=(commit_list_wrapper&&) noexcept = default;
45+
46+
size_t size() const;
47+
48+
private:
49+
50+
std::vector<commit_wrapper> m_commit_list;
51+
2952
};

0 commit comments

Comments
 (0)