Skip to content

Commit 04fe32f

Browse files
author
Edward Thomson
authored
Merge pull request #633 from libgit2/ethomson/merge_files
Introduce `Rugged::Blob.merge_files`
2 parents 57f4b3d + f6add44 commit 04fe32f

File tree

5 files changed

+241
-80
lines changed

5 files changed

+241
-80
lines changed

ext/rugged/rugged.c

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,87 @@ void rugged_rb_ary_to_strarray(VALUE rb_array, git_strarray *str_array)
428428
}
429429
}
430430

431+
void rugged_parse_merge_file_options(git_merge_file_options *opts, VALUE rb_options)
432+
{
433+
VALUE rb_value;
434+
435+
Check_Type(rb_options, T_HASH);
436+
437+
rb_value = rb_hash_aref(rb_options, CSTR2SYM("ancestor_label"));
438+
if (!NIL_P(rb_value)) {
439+
Check_Type(rb_value, T_STRING);
440+
opts->ancestor_label = StringValueCStr(rb_value);
441+
}
442+
443+
rb_value = rb_hash_aref(rb_options, CSTR2SYM("our_label"));
444+
if (!NIL_P(rb_value)) {
445+
Check_Type(rb_value, T_STRING);
446+
opts->our_label = StringValueCStr(rb_value);
447+
}
448+
449+
rb_value = rb_hash_aref(rb_options, CSTR2SYM("their_label"));
450+
if (!NIL_P(rb_value)) {
451+
Check_Type(rb_value, T_STRING);
452+
opts->their_label = StringValueCStr(rb_value);
453+
}
454+
455+
rb_value = rb_hash_aref(rb_options, CSTR2SYM("favor"));
456+
if (!NIL_P(rb_value)) {
457+
ID id_favor;
458+
459+
Check_Type(rb_value, T_SYMBOL);
460+
id_favor = SYM2ID(rb_value);
461+
462+
if (id_favor == rb_intern("normal")) {
463+
opts->favor = GIT_MERGE_FILE_FAVOR_NORMAL;
464+
} else if (id_favor == rb_intern("ours")) {
465+
opts->favor = GIT_MERGE_FILE_FAVOR_OURS;
466+
} else if (id_favor == rb_intern("theirs")) {
467+
opts->favor = GIT_MERGE_FILE_FAVOR_THEIRS;
468+
} else if (id_favor == rb_intern("union")) {
469+
opts->favor = GIT_MERGE_FILE_FAVOR_UNION;
470+
} else {
471+
rb_raise(rb_eTypeError,
472+
"Invalid favor mode. Expected `:normal`, `:ours`, `:theirs` or `:union`");
473+
}
474+
}
475+
476+
rb_value = rb_hash_aref(rb_options, CSTR2SYM("style"));
477+
if (!NIL_P(rb_value)) {
478+
ID id_style;
479+
480+
Check_Type(rb_value, T_SYMBOL);
481+
id_style = SYM2ID(rb_value);
482+
483+
if (id_style == rb_intern("standard")) {
484+
opts->flags |= GIT_MERGE_FILE_STYLE_MERGE;
485+
} else if (id_style == rb_intern("diff3")) {
486+
opts->flags |= GIT_MERGE_FILE_STYLE_DIFF3;
487+
} else {
488+
rb_raise(rb_eTypeError,
489+
"Invalid style mode. Expected `:standard`, or `:diff3`");
490+
}
491+
} else {
492+
opts->flags |= GIT_MERGE_FILE_STYLE_MERGE;
493+
}
494+
495+
if (RTEST(rb_hash_aref(rb_options, CSTR2SYM("simplify")))) {
496+
opts->flags |= GIT_MERGE_FILE_SIMPLIFY_ALNUM;
497+
}
498+
}
499+
500+
VALUE rb_merge_file_result_fromC(const git_merge_file_result *result)
501+
{
502+
VALUE rb_result = rb_hash_new();
503+
504+
rb_hash_aset(rb_result, CSTR2SYM("automergeable"), result->automergeable ? Qtrue : Qfalse);
505+
rb_hash_aset(rb_result, CSTR2SYM("path"), result->path ? rb_str_new_utf8(result->path) : Qnil);
506+
rb_hash_aset(rb_result, CSTR2SYM("filemode"), INT2FIX(result->mode));
507+
rb_hash_aset(rb_result, CSTR2SYM("data"), rb_str_new(result->ptr, result->len));
508+
509+
return rb_result;
510+
}
511+
431512
void Init_rugged(void)
432513
{
433514
rb_mRugged = rb_define_module("Rugged");

ext/rugged/rugged.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,12 @@ VALUE rugged_diff_hunk_new(VALUE owner, size_t hunk_idx, const git_diff_hunk *hu
9696
VALUE rugged_diff_line_new(const git_diff_line *line);
9797
VALUE rugged_remote_new(VALUE owner, git_remote *remote);
9898
VALUE rb_git_delta_file_fromC(const git_diff_file *file);
99+
VALUE rb_merge_file_result_fromC(const git_merge_file_result *results);
99100

100101
void rugged_parse_diff_options(git_diff_options *opts, VALUE rb_options);
101102
void rugged_parse_merge_options(git_merge_options *opts, VALUE rb_options);
102103
void rugged_parse_checkout_options(git_checkout_options *opts, VALUE rb_options);
104+
void rugged_parse_merge_file_options(git_merge_file_options *opts, VALUE rb_options);
103105

104106
void rugged_cred_extract(git_cred **cred, int allowed_types, VALUE rb_credential);
105107

ext/rugged/rugged_blob.c

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,108 @@ static VALUE rb_git_blob_to_buffer(int argc, VALUE *argv, VALUE self)
551551
return rb_ret;
552552
}
553553

554+
#define RUGGED_MERGE_FILE_INPUT_INIT { GIT_MERGE_FILE_INPUT_INIT }
555+
556+
typedef struct {
557+
git_merge_file_input parent;
558+
int has_id;
559+
git_oid id;
560+
} rugged_merge_file_input;
561+
562+
static void rugged_parse_merge_file_input(rugged_merge_file_input *input, git_repository *repo, VALUE rb_input)
563+
{
564+
VALUE rb_value;
565+
566+
Check_Type(rb_input, T_HASH);
567+
568+
if (!NIL_P(rb_value = rb_hash_aref(rb_input, CSTR2SYM("content")))) {
569+
input->parent.ptr = RSTRING_PTR(rb_value);
570+
input->parent.size = RSTRING_LEN(rb_value);
571+
} else if (!NIL_P(rb_value = rb_hash_aref(rb_input, CSTR2SYM("oid")))) {
572+
if (!repo)
573+
rb_raise(rb_eArgError, "Rugged repository is required when file input is `:oid`.");
574+
575+
rugged_exception_check(git_oid_fromstr(&input->id, RSTRING_PTR(rb_value)));
576+
input->has_id = 1;
577+
} else {
578+
rb_raise(rb_eArgError, "File input must have `:content` or `:oid`.");
579+
}
580+
581+
rb_value = rb_hash_aref(rb_input, CSTR2SYM("filemode"));
582+
if (!NIL_P(rb_value))
583+
input->parent.mode = FIX2UINT(rb_value);
584+
585+
rb_value = rb_hash_aref(rb_input, CSTR2SYM("path"));
586+
if (!NIL_P(rb_value)) {
587+
Check_Type(rb_value, T_STRING);
588+
input->parent.path = RSTRING_PTR(rb_value);
589+
}
590+
}
591+
592+
static int rugged_load_merge_file_input(git_blob **out, git_repository *repo, rugged_merge_file_input *input)
593+
{
594+
int error;
595+
596+
if (!input->has_id)
597+
return 0;
598+
599+
if ((error = git_blob_lookup(out, repo, &input->id)) < 0)
600+
return error;
601+
602+
input->parent.ptr = git_blob_rawcontent(*out);
603+
input->parent.size = git_blob_rawsize(*out);
604+
605+
return 0;
606+
}
607+
608+
static VALUE rb_git_blob_merge_files(int argc, VALUE *argv, VALUE klass)
609+
{
610+
VALUE rb_repo, rb_ancestor, rb_ours, rb_theirs, rb_options, rb_result;
611+
612+
git_repository *repo = NULL;
613+
rugged_merge_file_input ancestor = RUGGED_MERGE_FILE_INPUT_INIT,
614+
ours = RUGGED_MERGE_FILE_INPUT_INIT,
615+
theirs = RUGGED_MERGE_FILE_INPUT_INIT;
616+
git_blob *ancestor_blob = NULL, *our_blob = NULL, *their_blob = NULL;
617+
git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT;
618+
git_merge_file_result result = {0};
619+
int error;
620+
621+
rb_scan_args(argc, argv, "41", &rb_repo, &rb_ancestor, &rb_ours, &rb_theirs, &rb_options);
622+
623+
if (!NIL_P(rb_repo)) {
624+
rugged_check_repo(rb_repo);
625+
Data_Get_Struct(rb_repo, git_repository, repo);
626+
}
627+
628+
if (!NIL_P(rb_options))
629+
rugged_parse_merge_file_options(&opts, rb_options);
630+
631+
if (!NIL_P(rb_ancestor))
632+
rugged_parse_merge_file_input(&ancestor, repo, rb_ancestor);
633+
if (!NIL_P(rb_ours))
634+
rugged_parse_merge_file_input(&ours, repo, rb_ours);
635+
if (!NIL_P(rb_theirs))
636+
rugged_parse_merge_file_input(&theirs, repo, rb_theirs);
637+
638+
if ((error = rugged_load_merge_file_input(&ancestor_blob, repo, &ancestor)) < 0 ||
639+
(error = rugged_load_merge_file_input(&our_blob, repo, &ours)) < 0 ||
640+
(error = rugged_load_merge_file_input(&their_blob, repo, &theirs)) < 0 ||
641+
(error = git_merge_file(&result, &ancestor.parent, &ours.parent, &theirs.parent, &opts)) < 0)
642+
goto done;
643+
644+
rb_result = rb_merge_file_result_fromC(&result);
645+
646+
done:
647+
git_blob_free(ancestor_blob);
648+
git_blob_free(our_blob);
649+
git_blob_free(their_blob);
650+
git_merge_file_result_free(&result);
651+
652+
rugged_exception_check(error);
653+
return rb_result;
654+
}
655+
554656
static VALUE rb_git_blob_sig_new(int argc, VALUE *argv, VALUE klass)
555657
{
556658
int error, opts = 0;
@@ -622,6 +724,7 @@ void Init_rugged_blob(void)
622724
rb_define_singleton_method(rb_cRuggedBlob, "from_io", rb_git_blob_from_io, -1);
623725

624726
rb_define_singleton_method(rb_cRuggedBlob, "to_buffer", rb_git_blob_to_buffer, -1);
727+
rb_define_singleton_method(rb_cRuggedBlob, "merge_files", rb_git_blob_merge_files, -1);
625728

626729
rb_cRuggedBlobSig = rb_define_class_under(rb_cRuggedBlob, "HashSignature", rb_cObject);
627730
rb_define_singleton_method(rb_cRuggedBlobSig, "new", rb_git_blob_sig_new, -1);

ext/rugged/rugged_index.c

Lines changed: 3 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -952,76 +952,6 @@ static VALUE rb_git_conflict_get(VALUE self, VALUE rb_path)
952952
return rb_result;
953953
}
954954

955-
void rugged_parse_merge_file_options(git_merge_file_options *opts, VALUE rb_options)
956-
{
957-
if (!NIL_P(rb_options)) {
958-
VALUE rb_value;
959-
Check_Type(rb_options, T_HASH);
960-
961-
rb_value = rb_hash_aref(rb_options, CSTR2SYM("ancestor_label"));
962-
if (!NIL_P(rb_value)) {
963-
Check_Type(rb_value, T_STRING);
964-
opts->ancestor_label = StringValueCStr(rb_value);
965-
}
966-
967-
rb_value = rb_hash_aref(rb_options, CSTR2SYM("our_label"));
968-
if (!NIL_P(rb_value)) {
969-
Check_Type(rb_value, T_STRING);
970-
opts->our_label = StringValueCStr(rb_value);
971-
}
972-
973-
rb_value = rb_hash_aref(rb_options, CSTR2SYM("their_label"));
974-
if (!NIL_P(rb_value)) {
975-
Check_Type(rb_value, T_STRING);
976-
opts->their_label = StringValueCStr(rb_value);
977-
}
978-
979-
rb_value = rb_hash_aref(rb_options, CSTR2SYM("favor"));
980-
if (!NIL_P(rb_value)) {
981-
ID id_favor;
982-
983-
Check_Type(rb_value, T_SYMBOL);
984-
id_favor = SYM2ID(rb_value);
985-
986-
if (id_favor == rb_intern("normal")) {
987-
opts->favor = GIT_MERGE_FILE_FAVOR_NORMAL;
988-
} else if (id_favor == rb_intern("ours")) {
989-
opts->favor = GIT_MERGE_FILE_FAVOR_OURS;
990-
} else if (id_favor == rb_intern("theirs")) {
991-
opts->favor = GIT_MERGE_FILE_FAVOR_THEIRS;
992-
} else if (id_favor == rb_intern("union")) {
993-
opts->favor = GIT_MERGE_FILE_FAVOR_UNION;
994-
} else {
995-
rb_raise(rb_eTypeError,
996-
"Invalid favor mode. Expected `:normal`, `:ours`, `:theirs` or `:union`");
997-
}
998-
}
999-
1000-
rb_value = rb_hash_aref(rb_options, CSTR2SYM("style"));
1001-
if (!NIL_P(rb_value)) {
1002-
ID id_style;
1003-
1004-
Check_Type(rb_value, T_SYMBOL);
1005-
id_style = SYM2ID(rb_value);
1006-
1007-
if (id_style == rb_intern("standard")) {
1008-
opts->flags |= GIT_MERGE_FILE_STYLE_MERGE;
1009-
} else if (id_style == rb_intern("diff3")) {
1010-
opts->flags |= GIT_MERGE_FILE_STYLE_DIFF3;
1011-
} else {
1012-
rb_raise(rb_eTypeError,
1013-
"Invalid style mode. Expected `:standard`, or `:diff3`");
1014-
}
1015-
} else {
1016-
opts->flags |= GIT_MERGE_FILE_STYLE_MERGE;
1017-
}
1018-
1019-
if (RTEST(rb_hash_aref(rb_options, CSTR2SYM("simplify")))) {
1020-
opts->flags |= GIT_MERGE_FILE_SIMPLIFY_ALNUM;
1021-
}
1022-
}
1023-
}
1024-
1025955
/*
1026956
* call-seq:
1027957
* index.merge_file(path[, options]) -> merge_file or nil
@@ -1061,8 +991,7 @@ void rugged_parse_merge_file_options(git_merge_file_options *opts, VALUE rb_opti
1061991

1062992
static VALUE rb_git_merge_file(int argc, VALUE *argv, VALUE self)
1063993
{
1064-
VALUE rb_path, rb_options;
1065-
VALUE rb_result = rb_hash_new();
994+
VALUE rb_path, rb_options, rb_result;
1066995
VALUE rb_repo = rugged_owner(self);
1067996

1068997
git_repository *repo;
@@ -1074,10 +1003,8 @@ static VALUE rb_git_merge_file(int argc, VALUE *argv, VALUE self)
10741003

10751004
rb_scan_args(argc, argv, "1:", &rb_path, &rb_options);
10761005

1077-
if (!NIL_P(rb_options)) {
1078-
Check_Type(rb_options, T_HASH);
1006+
if (!NIL_P(rb_options))
10791007
rugged_parse_merge_file_options(&opts, rb_options);
1080-
}
10811008

10821009
Check_Type(rb_path, T_STRING);
10831010

@@ -1100,11 +1027,7 @@ static VALUE rb_git_merge_file(int argc, VALUE *argv, VALUE self)
11001027
error = git_merge_file_from_index(&merge_file_result, repo, ancestor, ours, theirs, &opts);
11011028
rugged_exception_check(error);
11021029

1103-
rb_hash_aset(rb_result, CSTR2SYM("automergeable"), merge_file_result.automergeable ? Qtrue : Qfalse);
1104-
rb_hash_aset(rb_result, CSTR2SYM("path"), rb_path);
1105-
rb_hash_aset(rb_result, CSTR2SYM("filemode"), INT2FIX(merge_file_result.mode));
1106-
rb_hash_aset(rb_result, CSTR2SYM("data"), rb_str_new(merge_file_result.ptr, merge_file_result.len));
1107-
1030+
rb_result = rb_merge_file_result_fromC(&merge_file_result);
11081031
git_merge_file_result_free(&merge_file_result);
11091032

11101033
return rb_result;

test/blob_test.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,58 @@ def test_blob_text_set_encoding
124124
end
125125
end
126126

127+
class BlobMergeTest < Rugged::TestCase
128+
def setup
129+
@source_repo = FixtureRepo.from_rugged("testrepo.git")
130+
@repo = FixtureRepo.clone(@source_repo)
131+
132+
@ancestor_content = "Common ancestor"
133+
@our_content = "Ours side"
134+
@their_content = "Theirs side"
135+
136+
@expected_data = "<<<<<<< OURS\nOurs side\n" +
137+
"||||||| ANCESTOR\nCommon ancestor\n" +
138+
"=======\n" +
139+
"Theirs side\n>>>>>>> THEIRS\n"
140+
141+
@opts = {
142+
:ancestor_label => "ANCESTOR",
143+
:our_label => "OURS",
144+
:their_label => "THEIRS",
145+
:style => :diff3 }
146+
end
147+
148+
def test_blob_merge_files
149+
ancestor = { :content => @ancestor_content, :path => "file.txt", :filemode => 0100644 }
150+
ours = { :content => @our_content, :path => "newfile.txt", :filemode => 0100644 }
151+
theirs = { :content => @their_content, :path => "file.txt", :filemode => 0100755 }
152+
153+
result = Rugged::Blob.merge_files(nil, ancestor, ours, theirs, @opts)
154+
155+
assert_equal false, result[:automergeable]
156+
assert_equal "newfile.txt", result[:path]
157+
assert_equal 0100755, result[:filemode]
158+
assert_equal @expected_data, result[:data]
159+
end
160+
161+
def test_blob_merge_files_by_oid
162+
ancestor_oid = Rugged::Blob.from_buffer(@repo, @ancestor_content)
163+
our_oid = Rugged::Blob.from_buffer(@repo, @our_content)
164+
their_oid = Rugged::Blob.from_buffer(@repo, @their_content)
165+
166+
ancestor = { :oid => ancestor_oid, :path => "file.txt", :filemode => 0100644 }
167+
ours = { :oid => our_oid, :path => "newfile.txt", :filemode => 0100644 }
168+
theirs = { :oid => their_oid, :path => "file.txt", :filemode => 0100755 }
169+
170+
result = Rugged::Blob.merge_files(@repo, ancestor, ours, theirs, @opts)
171+
172+
assert_equal false, result[:automergeable]
173+
assert_equal "newfile.txt", result[:path]
174+
assert_equal 0100755, result[:filemode]
175+
assert_equal @expected_data, result[:data]
176+
end
177+
end
178+
127179
class BlobWriteTest < Rugged::TestCase
128180
def setup
129181
@source_repo = FixtureRepo.from_rugged("testrepo.git")

0 commit comments

Comments
 (0)