Skip to content

Commit 69b86a4

Browse files
authored
Add merge subcommand (fastforward) (#48)
* Add merge subcommand (fastforward) * comment unused code * clean code * address review comments
1 parent e544bc4 commit 69b86a4

18 files changed

+361
-47
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 & 21 deletions
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
@@ -56,26 +56,6 @@ void checkout_subcommand::run()
5656
}
5757
}
5858

59-
std::optional<annotated_commit_wrapper> checkout_subcommand::resolve_local_ref
60-
(
61-
const repository_wrapper& repo,
62-
const std::string& target_name
63-
)
64-
{
65-
if (auto ref = repo.find_reference_dwim(target_name))
66-
{
67-
return repo.find_annotated_commit(*ref);
68-
}
69-
else if (auto obj = repo.revparse_single(target_name))
70-
{
71-
return repo.find_annotated_commit(obj->oid());
72-
}
73-
else
74-
{
75-
return std::nullopt;
76-
}
77-
}
78-
7959
annotated_commit_wrapper checkout_subcommand::create_local_branch
8060
(
8161
repository_wrapper& repo,

src/subcommand/checkout_subcommand.hpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,6 @@ class checkout_subcommand
1717

1818
private:
1919

20-
std::optional<annotated_commit_wrapper> resolve_local_ref
21-
(
22-
const repository_wrapper& repo,
23-
const std::string& target_name
24-
);
25-
2620
annotated_commit_wrapper create_local_branch
2721
(
2822
repository_wrapper& repo,

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: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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("<branch>", m_branches_to_merge, "Branch(es) to merge");
13+
14+
sub->callback([this]() { this->run(); });
15+
}
16+
17+
annotated_commit_list_wrapper merge_subcommand::resolve_heads(const repository_wrapper& repo)
18+
{
19+
std::vector<annotated_commit_wrapper> commits_to_merge;
20+
commits_to_merge.reserve(m_branches_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+
reference_wrapper target_ref = lambda_get_target_ref(&repo, is_unborn);
49+
50+
object_wrapper target = repo.find_object(target_oid, GIT_OBJECT_COMMIT);
51+
52+
repo.checkout_tree(target, ff_checkout_options);
53+
54+
target_ref.write_new_ref(target_oid);
55+
}
56+
57+
void merge_subcommand::run()
58+
{
59+
auto directory = get_current_git_path();
60+
auto bare = false;
61+
auto repo = repository_wrapper::open(directory);
62+
63+
auto state = repo.state();
64+
if (state != GIT_REPOSITORY_STATE_NONE)
65+
{
66+
std::cout << "repository is in unexpected state " << state <<std::endl;
67+
}
68+
69+
git_merge_analysis_t analysis;
70+
git_merge_preference_t preference;
71+
annotated_commit_list_wrapper commits_to_merge = resolve_heads(repo);
72+
size_t num_commits_to_merge = commits_to_merge.size();
73+
git_annotated_commit** c_commits_to_merge = commits_to_merge;
74+
auto commits_to_merge_const = const_cast<const git_annotated_commit**>(c_commits_to_merge);
75+
76+
throw_if_error(git_merge_analysis(&analysis, &preference, repo, commits_to_merge_const, num_commits_to_merge));
77+
78+
if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
79+
{
80+
std::cout << "Already up-to-date" << std::endl;
81+
}
82+
else if (analysis & GIT_MERGE_ANALYSIS_UNBORN ||
83+
(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD &&
84+
!(preference & GIT_MERGE_PREFERENCE_NO_FASTFORWARD)))
85+
{
86+
if (analysis & GIT_MERGE_ANALYSIS_UNBORN)
87+
{
88+
std::cout << "Unborn" << std::endl;
89+
}
90+
else
91+
{
92+
std::cout << "Fast-forward" << std::endl;
93+
}
94+
const annotated_commit_wrapper& commit = commits_to_merge.front();
95+
const git_oid target_oid = commit.oid();
96+
// Since this is a fast-forward, there can be only one merge head.
97+
assert(num_commits_to_merge == 1);
98+
perform_fastforward(repo, target_oid, (analysis & GIT_MERGE_ANALYSIS_UNBORN));
99+
}
100+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include <CLI/CLI.hpp>
4+
5+
#include "../utils/common.hpp"
6+
#include "../wrapper/repository_wrapper.hpp"
7+
8+
class merge_subcommand
9+
{
10+
public:
11+
12+
explicit merge_subcommand(const libgit2_object&, CLI::App& app);
13+
void run();
14+
15+
private:
16+
17+
annotated_commit_list_wrapper resolve_heads(const repository_wrapper& repo);
18+
19+
std::vector<std::string> m_branches_to_merge;
20+
};

src/wrapper/annotated_commit_wrapper.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ class annotated_commit_wrapper : public wrapper_base<git_annotated_commit>
2727
friend class repository_wrapper;
2828
};
2929

30+
using annotated_commit_list_wrapper = list_wrapper<annotated_commit_wrapper>;

src/wrapper/commit_wrapper.hpp

Lines changed: 4 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>
@@ -28,3 +29,5 @@ class commit_wrapper : public wrapper_base<git_commit>
2829
friend class repository_wrapper;
2930
friend class reference_wrapper;
3031
};
32+
33+
using commit_list_wrapper = list_wrapper<commit_wrapper>;

src/wrapper/refs_wrapper.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#include "../utils/git_exception.hpp"
2+
#include "object_wrapper.hpp"
3+
#include <git2/refs.h>
4+
#include <git2/types.h>
25
#include "../wrapper/refs_wrapper.hpp"
36

47
reference_wrapper::reference_wrapper(git_reference* ref)
@@ -21,3 +24,15 @@ bool reference_wrapper::is_remote() const
2124
{
2225
return git_reference_is_remote(*this);
2326
}
27+
28+
const git_oid* reference_wrapper::target() const
29+
{
30+
return git_reference_target(p_resource);
31+
}
32+
33+
reference_wrapper reference_wrapper::write_new_ref(const git_oid target_oid)
34+
{
35+
git_reference* new_ref;
36+
throw_if_error(git_reference_set_target(&new_ref, p_resource, &target_oid, NULL));
37+
return reference_wrapper(new_ref);
38+
}

0 commit comments

Comments
 (0)