Skip to content

Commit a0741af

Browse files
committed
Implemented rebase
1 parent 7c42092 commit a0741af

File tree

7 files changed

+839
-0
lines changed

7 files changed

+839
-0
lines changed

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ set(GIT2CPP_SRC
6262
${GIT2CPP_SOURCE_DIR}/subcommand/merge_subcommand.hpp
6363
${GIT2CPP_SOURCE_DIR}/subcommand/push_subcommand.cpp
6464
${GIT2CPP_SOURCE_DIR}/subcommand/push_subcommand.hpp
65+
${GIT2CPP_SOURCE_DIR}/subcommand/rebase_subcommand.cpp
66+
${GIT2CPP_SOURCE_DIR}/subcommand/rebase_subcommand.hpp
6567
${GIT2CPP_SOURCE_DIR}/subcommand/remote_subcommand.cpp
6668
${GIT2CPP_SOURCE_DIR}/subcommand/remote_subcommand.hpp
6769
${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.cpp
@@ -98,6 +100,8 @@ set(GIT2CPP_SRC
98100
${GIT2CPP_SOURCE_DIR}/wrapper/index_wrapper.hpp
99101
${GIT2CPP_SOURCE_DIR}/wrapper/object_wrapper.cpp
100102
${GIT2CPP_SOURCE_DIR}/wrapper/object_wrapper.hpp
103+
${GIT2CPP_SOURCE_DIR}/wrapper/rebase_wrapper.cpp
104+
${GIT2CPP_SOURCE_DIR}/wrapper/rebase_wrapper.hpp
101105
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.cpp
102106
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.hpp
103107
${GIT2CPP_SOURCE_DIR}/wrapper/remote_wrapper.cpp

src/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "subcommand/log_subcommand.hpp"
1717
#include "subcommand/merge_subcommand.hpp"
1818
#include "subcommand/push_subcommand.hpp"
19+
#include "subcommand/rebase_subcommand.hpp"
1920
#include "subcommand/remote_subcommand.hpp"
2021
#include "subcommand/reset_subcommand.hpp"
2122
#include "subcommand/stash_subcommand.hpp"
@@ -48,6 +49,7 @@ int main(int argc, char** argv)
4849
log_subcommand log(lg2_obj, app);
4950
merge_subcommand merge(lg2_obj, app);
5051
push_subcommand push(lg2_obj, app);
52+
rebase_subcommand rebase(lg2_obj, app);
5153
remote_subcommand remote(lg2_obj, app);
5254
revparse_subcommand revparse(lg2_obj, app);
5355
revlist_subcommand revlist(lg2_obj, app);
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
#include <iostream>
2+
#include <git2.h>
3+
#include <termcolor/termcolor.hpp>
4+
5+
#include "rebase_subcommand.hpp"
6+
#include "../utils/git_exception.hpp"
7+
#include "../wrapper/repository_wrapper.hpp"
8+
#include "../wrapper/signature_wrapper.hpp"
9+
#include "../wrapper/index_wrapper.hpp"
10+
11+
rebase_subcommand::rebase_subcommand(const libgit2_object&, CLI::App& app)
12+
{
13+
auto *sub = app.add_subcommand("rebase", "Reapply commits on top of another base tip");
14+
15+
sub->add_option("upstream", m_upstream, "Upstream branch to rebase onto");
16+
sub->add_option("branch", m_branch, "Working branch; defaults to HEAD");
17+
sub->add_option("--onto", m_onto, "Starting point at which to create the new commits");
18+
19+
sub->add_flag("--abort", m_abort, "Abort the rebase operation and reset HEAD to the original branch");
20+
sub->add_flag("--continue", m_continue, "Restart the rebasing process after having resolved a merge conflict");
21+
sub->add_flag("--skip", m_skip, "Restart the rebasing process by skipping the current patch");
22+
sub->add_flag("--quit", m_quit, "Abort the rebase operation but HEAD is not reset back to the original branch");
23+
24+
sub->callback([this]() { this->run(); });
25+
}
26+
27+
void ensure_rebase_in_progress(git_repository_state_t state)
28+
{
29+
if (state != GIT_REPOSITORY_STATE_REBASE_INTERACTIVE &&
30+
state != GIT_REPOSITORY_STATE_REBASE_MERGE)
31+
{
32+
throw std::runtime_error("No rebase in progress");
33+
}
34+
}
35+
36+
void rebase_subcommand::run()
37+
{
38+
auto directory = get_current_git_path();
39+
auto repo = repository_wrapper::open(directory);
40+
41+
git_repository_state_t state = repo.state();
42+
43+
if (m_abort)
44+
{
45+
ensure_rebase_in_progress(state);
46+
run_abort(repo);
47+
return;
48+
}
49+
50+
if (m_continue)
51+
{
52+
ensure_rebase_in_progress(state);
53+
run_continue(repo);
54+
return;
55+
}
56+
57+
if (m_skip)
58+
{
59+
ensure_rebase_in_progress(state);
60+
run_skip(repo);
61+
return;
62+
}
63+
64+
if (m_quit)
65+
{
66+
ensure_rebase_in_progress(state);
67+
run_quit(repo);
68+
return;
69+
}
70+
71+
if (state == GIT_REPOSITORY_STATE_REBASE_INTERACTIVE ||
72+
state == GIT_REPOSITORY_STATE_REBASE_MERGE)
73+
{
74+
throw std::runtime_error("A rebase is already in progress");
75+
}
76+
77+
if (state != GIT_REPOSITORY_STATE_NONE)
78+
{
79+
throw std::runtime_error("Cannot rebase: repository is in unexpected state");
80+
}
81+
82+
run_rebase(repo);
83+
}
84+
85+
annotated_commit_wrapper rebase_subcommand::resolve_ref(
86+
const repository_wrapper& repo,
87+
const std::string& ref_name) const
88+
{
89+
if (!ref_name.empty())
90+
{
91+
auto branch_commit = repo.resolve_local_ref(m_branch);
92+
if (!branch_commit)
93+
{
94+
throw std::runtime_error("error: could not resolve branch '" + m_branch + "'");
95+
}
96+
return std::move(branch_commit).value();
97+
}
98+
else
99+
{
100+
auto head = repo.head();
101+
auto branch_commit = repo.find_annotated_commit(head);
102+
return branch_commit;
103+
}
104+
}
105+
106+
void rebase_subcommand::perform_rebase(repository_wrapper& repo, rebase_wrapper& rebase)
107+
{
108+
auto signatures = signature_wrapper::get_default_signature_from_env(repo);
109+
110+
size_t total_operations = rebase.operation_entry_count();
111+
std::cout << "Rebasing " << total_operations << " commit(s)..." << std::endl;
112+
113+
while (rebase.next_operation())
114+
{
115+
size_t current_idx = rebase.current_operation_index();
116+
117+
commit_wrapper original_commit = repo.find_commit(rebase.current_operation_id());
118+
std::string commit_summary = original_commit.summary();
119+
120+
std::cout << "Applying: " << commit_summary << " ("
121+
<< (current_idx + 1) << "/" << total_operations << ")" << std::endl;
122+
123+
index_wrapper index = repo.make_index();
124+
if (index.has_conflict())
125+
{
126+
std:: cout << termcolor::red << "Conflicts detected!" << termcolor::reset << std::endl;
127+
std::cout << "Resolve conflicts and run:" << std::endl;
128+
std::cout << " git2cpp rebase --continue" << std::endl;
129+
std::cout << "or skip this commit with:" << std::endl;
130+
std::cout << " git2cpp rebase --skip" << std:: endl;
131+
std::cout << "or abort the rebase with:" << std:: endl;
132+
std::cout << " git2cpp rebase --abort" << std::endl;
133+
return;
134+
}
135+
136+
int commit_result = rebase.commit(signatures.first, signatures.second);
137+
138+
if (commit_result == GIT_EAPPLIED)
139+
{
140+
std::cout << termcolor::yellow << "Skipping commit (already applied)"
141+
<< termcolor::reset << std::endl;
142+
}
143+
else
144+
{
145+
throw_if_error(commit_result);
146+
}
147+
}
148+
149+
rebase.finish(signatures.second);
150+
151+
std::cout << termcolor::green << "Successfully rebased and updated HEAD."
152+
<< termcolor::reset << std::endl;
153+
}
154+
155+
void rebase_subcommand::run_rebase(repository_wrapper& repo)
156+
{
157+
if (m_upstream.empty())
158+
{
159+
throw std::runtime_error("upstream is required for rebase");
160+
}
161+
162+
annotated_commit_wrapper branch = resolve_ref(repo, m_branch);
163+
std::optional<annotated_commit_wrapper> upstream = repo.resolve_local_ref(m_upstream);
164+
if (!upstream)
165+
{
166+
throw std::runtime_error("error: could not resolve upstream '" + m_upstream + "'");
167+
}
168+
169+
std::unique_ptr<annotated_commit_wrapper> onto_ptr = nullptr;
170+
if (!m_onto.empty())
171+
{
172+
onto_ptr = std::make_unique<annotated_commit_wrapper>(resolve_ref(repo, m_onto));
173+
}
174+
git_rebase_options rebase_opts ;
175+
throw_if_error(git_rebase_options_init(&rebase_opts, GIT_REBASE_OPTIONS_VERSION));
176+
177+
auto rebase = rebase_wrapper::init(repo, branch, upstream.value(), onto_ptr.get(), rebase_opts);
178+
perform_rebase(repo, rebase);
179+
}
180+
181+
void rebase_subcommand::run_abort(repository_wrapper& repo)
182+
{
183+
git_rebase_options rebase_opts;
184+
throw_if_error(git_rebase_options_init(&rebase_opts, GIT_REBASE_OPTIONS_VERSION));
185+
auto rebase = rebase_wrapper::open(repo, rebase_opts);
186+
rebase.abort();
187+
std::cout << "Rebase aborted" << std::endl;
188+
}
189+
190+
void rebase_subcommand::run_continue(repository_wrapper& repo)
191+
{
192+
// Check if there are still conflicts
193+
index_wrapper index = repo.make_index();
194+
if (index.has_conflict())
195+
{
196+
throw std::runtime_error("You must resolve conflicts before continuing the rebase");
197+
}
198+
git_rebase_options rebase_opts;
199+
throw_if_error(git_rebase_options_init(&rebase_opts, GIT_REBASE_OPTIONS_VERSION));
200+
auto rebase = rebase_wrapper::open(repo, rebase_opts);
201+
202+
// Get signature for commits
203+
auto signatures = signature_wrapper::get_default_signature_from_env(repo);
204+
205+
int commit_result = rebase.commit(signatures.first, signatures.second);
206+
207+
if (commit_result == GIT_EAPPLIED)
208+
{
209+
std::cout << termcolor::yellow << "Skipping commit (already applied)"
210+
<< termcolor::reset << std::endl;
211+
}
212+
else
213+
{
214+
throw_if_error(commit_result);
215+
}
216+
217+
perform_rebase(repo, rebase);
218+
}
219+
220+
void rebase_subcommand::run_skip(repository_wrapper& repo)
221+
{
222+
git_rebase_options rebase_opts;
223+
throw_if_error(git_rebase_options_init(&rebase_opts, GIT_REBASE_OPTIONS_VERSION));
224+
auto rebase = rebase_wrapper::open(repo, rebase_opts);
225+
226+
std::cout << "Skipping current commit..." << std::endl;
227+
228+
perform_rebase(repo, rebase);
229+
}
230+
231+
void rebase_subcommand:: run_quit(repository_wrapper& repo)
232+
{
233+
repo.state_cleanup();
234+
std::cout << "Rebase state cleaned up (HEAD not reset)" << std::endl;
235+
}
236+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
#include <CLI/CLI.hpp>
6+
7+
#include "../utils/common.hpp"
8+
#include "../wrapper/rebase_wrapper.hpp"
9+
#include "../wrapper/repository_wrapper.hpp"
10+
11+
class rebase_subcommand
12+
{
13+
public:
14+
15+
explicit rebase_subcommand(const libgit2_object&, CLI::App& app);
16+
void run();
17+
18+
private:
19+
20+
void run_rebase(repository_wrapper& repo);
21+
void run_abort(repository_wrapper& repo);
22+
void run_continue(repository_wrapper& repo);
23+
void run_skip(repository_wrapper& repo);
24+
void run_quit(repository_wrapper& repo);
25+
26+
annotated_commit_wrapper resolve_ref(const repository_wrapper& repo, const std:: string& ref_name) const;
27+
28+
void perform_rebase(repository_wrapper& repo, rebase_wrapper& rebase);
29+
30+
std::string m_upstream = {};
31+
std::string m_branch = {};
32+
std::string m_onto = {};
33+
34+
bool m_abort = false;
35+
bool m_continue = false;
36+
bool m_skip = false;
37+
bool m_quit = false;
38+
};

src/wrapper/rebase_wrapper.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#include "rebase_wrapper.hpp"
2+
#include "../utils/git_exception.hpp"
3+
4+
#include <iostream>
5+
6+
rebase_wrapper::~rebase_wrapper()
7+
{
8+
git_rebase_free(p_resource);
9+
p_resource = nullptr;
10+
}
11+
12+
bool rebase_wrapper::next_operation()
13+
{
14+
int res = git_rebase_next(&p_operation, p_resource);
15+
return res == 0;
16+
}
17+
18+
git_oid rebase_wrapper::current_operation_id() const
19+
{
20+
return p_operation->id;
21+
}
22+
std::size_t rebase_wrapper::current_operation_index() const
23+
{
24+
return git_rebase_operation_current(p_resource);
25+
}
26+
27+
std::size_t rebase_wrapper::operation_entry_count() const
28+
{
29+
return git_rebase_operation_entrycount(p_resource);
30+
}
31+
32+
int rebase_wrapper::commit(const signature_wrapper& author, const signature_wrapper& committer)
33+
{
34+
git_oid new_commit_oid;
35+
int res = git_rebase_commit(
36+
&new_commit_oid,
37+
p_resource,
38+
author,
39+
committer,
40+
nullptr, // message encoding (NULL for default)
41+
nullptr); // message (NULL to use original)
42+
return res;
43+
}
44+
45+
void rebase_wrapper::finish(const signature_wrapper& committer)
46+
{
47+
throw_if_error(git_rebase_finish(p_resource, committer));
48+
p_resource = nullptr;
49+
}
50+
51+
void rebase_wrapper::abort()
52+
{
53+
throw_if_error(git_rebase_abort(p_resource));
54+
}
55+
56+
rebase_wrapper rebase_wrapper::init
57+
(
58+
repository_wrapper& repo,
59+
const annotated_commit_wrapper& branch,
60+
const annotated_commit_wrapper& upstream,
61+
const annotated_commit_wrapper* onto,
62+
const git_rebase_options& opts
63+
)
64+
{
65+
rebase_wrapper wp;
66+
const git_annotated_commit* raw_onto = nullptr;
67+
if (onto)
68+
{
69+
raw_onto = *onto;
70+
}
71+
throw_if_error(git_rebase_init(&(wp.p_resource), repo, branch, upstream, raw_onto, &opts));
72+
return wp;
73+
}
74+
75+
rebase_wrapper rebase_wrapper::open(repository_wrapper& repo, const git_rebase_options& opts)
76+
{
77+
rebase_wrapper wp;
78+
throw_if_error(git_rebase_open(&(wp.p_resource), repo, &opts));
79+
return wp;
80+
}

0 commit comments

Comments
 (0)