Skip to content

Commit 72a2fc2

Browse files
authored
Merge conflict (#60)
Merge conflicts
1 parent 9e6938b commit 72a2fc2

File tree

10 files changed

+452
-91
lines changed

10 files changed

+452
-91
lines changed

src/subcommand/merge_subcommand.cpp

Lines changed: 179 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
#include <cassert>
2+
#include <git2/reset.h>
23
#include <git2/types.h>
4+
#include <iostream>
5+
#include <termcolor/termcolor.hpp>
36

47
#include "merge_subcommand.hpp"
5-
#include <iostream>
8+
#include "../wrapper/status_wrapper.hpp"
69

710

811
merge_subcommand::merge_subcommand(const libgit2_object&, CLI::App& app)
912
{
1013
auto *sub = app.add_subcommand("merge", "Join two or more development histories together");
1114

1215
sub->add_option("<branch>", m_branches_to_merge, "Branch(es) to merge");
13-
// sub->add_flag("--no-ff", m_no_ff, "");
16+
// sub->add_flag("--no-ff", m_no_ff, "Create a merge commit in all cases, even when the merge could instead be resolved as a fast-forward.");
1417
// sub->add_flag("--commit", m_commit, "Perform the merge and commit the result. This option can be used to override --no-commit.");
1518
sub->add_flag("--no-commit", m_no_commit, "With --no-commit perform the merge and stop just before creating a merge commit, to give the user a chance to inspect and further tweak the merge result before committing. \nNote that fast-forward updates do not create a merge commit and therefore there is no way to stop those merges with --no-commit. Thus, if you want to ensure your branch is not changed or updated by the merge command, use --no-ff with --no-commit.");
19+
sub->add_flag("--abort", m_abort, "Abort the current conflict resolution process, and try to reconstruct the pre-merge state. If an autostash entry is present, apply it to the worktree.\nIf there were uncommitted worktree changes present when the merge started, git merge --abort will in some cases be unable to reconstruct these changes. It is therefore recommended to always commit or stash your changes before running git merge.\ngit merge --abort is equivalent to git reset --merge when MERGE_HEAD is present unless MERGE_AUTOSTASH is also present in which case git merge --abort applies the stash entry to the worktree whereas git reset --merge will save the stashed changes in the stash list.");
20+
sub->add_flag("--quit", m_quit, "Forget about the current merge in progress. Leave the index and the working tree as-is. If MERGE_AUTOSTASH is present, the stash entry will be saved to the stash list.");
21+
sub->add_flag("--continue", m_continue, "After a git merge stops due to conflicts you can conclude the merge by running git merge --continue"); // (see "HOW TO RESOLVE CONFLICTS" section below).
1622

1723
sub->callback([this]() { this->run(); });
1824
}
@@ -33,7 +39,23 @@ annotated_commit_list_wrapper merge_subcommand::resolve_heads(const repository_w
3339
return annotated_commit_list_wrapper(std::move(commits_to_merge));
3440
}
3541

36-
void perform_fastforward(repository_wrapper& repo, const git_oid target_oid, int is_unborn)
42+
annotated_commit_list_wrapper resolve_mergeheads(const repository_wrapper& repo, const std::vector<git_oid>& oid_list)
43+
{
44+
std::vector<annotated_commit_wrapper> commits_to_merge;
45+
commits_to_merge.reserve(oid_list.size());
46+
47+
for (const auto& id:oid_list)
48+
{
49+
std::optional<annotated_commit_wrapper> commit = repo.find_annotated_commit(id);
50+
if (commit.has_value())
51+
{
52+
commits_to_merge.push_back(std::move(commit).value());
53+
}
54+
}
55+
return annotated_commit_list_wrapper(std::move(commits_to_merge));
56+
}
57+
58+
void perform_fastforward(repository_wrapper& repo, const git_oid& target_oid, int is_unborn)
3759
{
3860
const git_checkout_options ff_checkout_options = GIT_CHECKOUT_OPTIONS_INIT;
3961

@@ -60,12 +82,13 @@ void perform_fastforward(repository_wrapper& repo, const git_oid target_oid, int
6082
void merge_subcommand::create_merge_commit(
6183
repository_wrapper& repo,
6284
const index_wrapper& index,
85+
const std::vector<std::string>& branches_to_merge,
6386
const annotated_commit_list_wrapper& commits_to_merge,
6487
size_t num_commits_to_merge)
6588
{
6689
auto head_ref = repo.head();
67-
auto merge_ref = repo.find_reference_dwim(m_branches_to_merge.front());
68-
auto merge_commit = repo.resolve_local_ref(m_branches_to_merge.front()).value();
90+
auto merge_ref = repo.find_reference_dwim(branches_to_merge.front());
91+
auto merge_commit = repo.resolve_local_ref(branches_to_merge.front()).value();
6992

7093
std::vector<commit_wrapper> parents_list;
7194
parents_list.reserve(num_commits_to_merge + 1);
@@ -85,6 +108,7 @@ void merge_subcommand::create_merge_commit(
85108

86109
// TODO: add a prompt to edit the merge message
87110
std::string msg_target = merge_ref ? merge_ref->short_name() : git_oid_tostr_s(&(merge_commit.oid()));
111+
msg_target = "\'" + msg_target + "\'";
88112
std::string msg = merge_ref ? "Merge branch " : "Merge commit ";
89113
msg.append(msg_target);
90114

@@ -93,15 +117,159 @@ void merge_subcommand::create_merge_commit(
93117
repo.state_cleanup();
94118
}
95119

120+
// This function is used as a callback in git_repository_mergehead_foreach and therefore its type must be git_repository_mergehead_foreach_cb.
121+
int populate_list(const git_oid* oid, void* payload)
122+
{
123+
auto* l = reinterpret_cast<std::vector<git_oid>*>(payload);
124+
l->push_back(*oid);
125+
return 0;
126+
}
127+
96128
void merge_subcommand::run()
97129
{
98130
auto directory = get_current_git_path();
99131
auto bare = false;
100132
auto repo = repository_wrapper::open(directory);
101-
102133
auto state = repo.state();
103-
if (state != GIT_REPOSITORY_STATE_NONE)
134+
index_wrapper index = repo.make_index();
135+
stream_colour_fn yellow = termcolor::yellow;
136+
137+
if (state == GIT_REPOSITORY_STATE_MERGE)
104138
{
139+
if (m_abort)
140+
{
141+
// git merge --abort is equivalent to git reset --merge when MERGE_HEAD is present
142+
// unless MERGE_AUTOSTASH is also present in which case git merge --abort applies
143+
// the stash entry to the worktree whereas git reset --merge will save the stashed
144+
// changes in the stash list.
145+
146+
if (m_quit | m_continue)
147+
{
148+
std::cout << "fatal: --abort expects no arguments" << std::endl; // TODO: add the help info
149+
return;
150+
}
151+
152+
std::cout << "Warning: 'merge --abort' is not implemented yet. A 'reset --hard HEAD' will be executed." << std::endl;
153+
std::cout << "Do you want to continue [y/N] ?" << std::endl;
154+
std::string answer;
155+
std::cin >> answer;
156+
if (answer == "y")
157+
{
158+
repo.state_cleanup();
159+
index.conflict_cleanup();
160+
161+
git_checkout_options options;
162+
git_checkout_options_init(&options, GIT_CHECKOUT_OPTIONS_VERSION);
163+
auto head_ref = repo.head();
164+
repo.reset(head_ref.peel<object_wrapper>(), GIT_RESET_HARD, options);
165+
}
166+
else
167+
{
168+
std::cout << "Abort." << std::endl; // maybe another message would be more clear?
169+
}
170+
return;
171+
}
172+
else if (m_quit)
173+
{
174+
// Forget about the current merge in progress. Leave the index and the working tree as-is.
175+
// If MERGE_AUTOSTASH is present, the stash entry will be saved to the stash list.
176+
//
177+
178+
// if (m_continue)
179+
// {
180+
// std::cout << "fatal: --abort expects no arguments" << std::endl; // TODO: add the help info
181+
// return;
182+
// }
183+
184+
// problem: can't do a reset if the state is not cleaned up, but it shouldn't be.
185+
// Idem for the index and the conflicts.
186+
187+
// repo.state_cleanup();
188+
// index.conflict_cleanup();
189+
190+
// git_checkout_options options;
191+
// git_checkout_options_init(&options, GIT_CHECKOUT_OPTIONS_VERSION);
192+
// auto head_ref = repo.head();
193+
// repo.reset(head_ref.peel<object_wrapper>(), GIT_RESET_SOFT, options);
194+
195+
std::cout << "merge --quit is not implemented yet." << std::endl;
196+
return;
197+
}
198+
else if (m_continue)
199+
{
200+
auto sl = status_list_wrapper::status_list(repo);
201+
if (!sl.has_unmerged_header())
202+
{
203+
// std::string commit_message = "Merge branch "; // how to get the name of the branch the merge was started on ?
204+
// auto author_committer_signatures = signature_wrapper::get_default_signature_from_env(repo);
205+
// repo.create_commit(author_committer_signatures, commit_message, std::nullopt);
206+
207+
std::vector<git_oid> oid_list;
208+
git_repository_mergehead_foreach(repo, populate_list, &oid_list);
209+
210+
annotated_commit_list_wrapper commits_to_merge = resolve_mergeheads(repo, oid_list);
211+
size_t num_commits_to_merge = commits_to_merge.size();
212+
213+
std::vector<std::string> branches_to_merge_names;
214+
for (const auto& id:oid_list)
215+
{
216+
git_reference_iterator* iter;
217+
git_reference_iterator_new(&iter, repo);
218+
git_reference* ref;
219+
git_reference_next(&ref, iter);
220+
if (git_oid_equal(git_reference_target(ref), &id))
221+
{
222+
auto name = git_reference_name(ref);
223+
branches_to_merge_names.push_back(name);
224+
}
225+
git_reference_free(ref);
226+
}
227+
228+
create_merge_commit(repo, index, branches_to_merge_names, commits_to_merge, num_commits_to_merge);
229+
std::cout << "Merge made" << std::endl; // TODO: change the outpout to something like this: 3c22161 (HEAD -> master) Merge branch 'foregone'
230+
231+
repo.state_cleanup();
232+
index.conflict_cleanup();
233+
return;
234+
}
235+
else
236+
{
237+
auto entry_status = get_status_msg(GIT_STATUS_CONFLICTED).short_mod;
238+
const auto& entry_list = sl.get_entry_list(GIT_STATUS_CONFLICTED);
239+
for (auto* entry : entry_list)
240+
{
241+
git_diff_delta* diff_delta = entry->head_to_index; //ou entry->index_to_workdir ???
242+
const char* old_path = diff_delta->old_file.path;
243+
std::cout << entry_status << "\t" << old_path << std::endl;
244+
}
245+
std::cout << "error: Committing is not possible because you have unmerged files." << std::endl;
246+
}
247+
}
248+
else
249+
{
250+
std::cout << "error: Merging is not possible because you have unmerged files." << std::endl;
251+
}
252+
std::cout << yellow << "hint: Fix them up in the work tree, and then use 'git add/rm <file>'" << std::endl;
253+
std::cout << "hint: as appropriate to mark resolution and make a commit." << termcolor::reset << std::endl;
254+
std::cout << "fatal: Exiting because of an unresolved conflict." << std::endl;
255+
return;
256+
}
257+
else
258+
{
259+
if (m_abort)
260+
{
261+
std::cout << "fatal: There is no merge to abort (MERGE_HEAD missing)." << std::endl;
262+
return;
263+
}
264+
if (m_continue)
265+
{
266+
std::cout << "fatal: There is no merge in progress (MERGE_HEAD missing)." << std::endl;
267+
return;
268+
}
269+
}
270+
271+
if (state != GIT_REPOSITORY_STATE_NONE) // Could this be a "else if before the "else" above ?
272+
{
105273
std::cout << "repository is in unexpected state " << state <<std::endl;
106274
}
107275

@@ -159,17 +327,13 @@ void merge_subcommand::run()
159327
&checkout_opts));
160328
}
161329

162-
index_wrapper index = repo.make_index();
163-
164-
if (git_index_has_conflicts(index))
330+
if (index.has_conflict())
165331
{
166-
std::cout << "Conflict. To be implemented" << std::endl;
167-
/* Handle conflicts */
168-
// output_conflicts(index);
332+
index.output_conflicts();
169333
}
170334
else if (!m_no_commit)
171335
{
172-
create_merge_commit(repo, index, commits_to_merge, num_commits_to_merge);
173-
printf("Merge made\n");
336+
create_merge_commit(repo, index, m_branches_to_merge, commits_to_merge, num_commits_to_merge);
337+
std::cout << "Merge made" << std::endl;
174338
}
175339
}

src/subcommand/merge_subcommand.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@ class merge_subcommand
1818
void create_merge_commit(
1919
repository_wrapper& repo,
2020
const index_wrapper& index,
21+
const std::vector<std::string>& branches_to_merge,
2122
const annotated_commit_list_wrapper& commits_to_merge,
2223
size_t num_commits_to_merge);
2324

2425
std::vector<std::string> m_branches_to_merge;
2526
// bool m_no_ff = false;
2627
// bool m_commit = false;
2728
bool m_no_commit = false;
29+
bool m_abort = false;
30+
bool m_quit = false;
31+
bool m_continue = false;
2832
};

0 commit comments

Comments
 (0)