2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
10 #include "repository.h"
14 #include "diff_xdiff.h"
17 #include "git2/repository.h"
18 #include "git2/object.h"
19 #include "git2/index.h"
20 #include "git2/merge.h"
22 #include "xdiff/xdiff.h"
24 /* only examine the first 8000 bytes for binaryness.
25 * https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/xdiff-interface.c#L197
27 #define GIT_MERGE_FILE_BINARY_SIZE 8000
29 #define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0)
31 static int merge_file_input_from_index(
32 git_merge_file_input
*input_out
,
33 git_odb_object
**odb_object_out
,
35 const git_index_entry
*entry
)
39 GIT_ASSERT_ARG(input_out
);
40 GIT_ASSERT_ARG(odb_object_out
);
42 GIT_ASSERT_ARG(entry
);
44 if ((error
= git_odb_read(odb_object_out
, odb
, &entry
->id
)) < 0)
47 input_out
->path
= entry
->path
;
48 input_out
->mode
= entry
->mode
;
49 input_out
->ptr
= (char *)git_odb_object_data(*odb_object_out
);
50 input_out
->size
= git_odb_object_size(*odb_object_out
);
56 static void merge_file_normalize_opts(
57 git_merge_file_options
*out
,
58 const git_merge_file_options
*given_opts
)
61 memcpy(out
, given_opts
, sizeof(git_merge_file_options
));
63 git_merge_file_options default_opts
= GIT_MERGE_FILE_OPTIONS_INIT
;
64 memcpy(out
, &default_opts
, sizeof(git_merge_file_options
));
68 static int merge_file__xdiff(
69 git_merge_file_result
*out
,
70 const git_merge_file_input
*ancestor
,
71 const git_merge_file_input
*ours
,
72 const git_merge_file_input
*theirs
,
73 const git_merge_file_options
*given_opts
)
76 mmfile_t ancestor_mmfile
= {0}, our_mmfile
= {0}, their_mmfile
= {0};
78 git_merge_file_options options
= GIT_MERGE_FILE_OPTIONS_INIT
;
83 memset(out
, 0x0, sizeof(git_merge_file_result
));
85 merge_file_normalize_opts(&options
, given_opts
);
87 memset(&xmparam
, 0x0, sizeof(xmparam_t
));
90 xmparam
.ancestor
= (options
.ancestor_label
) ?
91 options
.ancestor_label
: ancestor
->path
;
92 ancestor_mmfile
.ptr
= (char *)ancestor
->ptr
;
93 ancestor_mmfile
.size
= ancestor
->size
;
96 xmparam
.file1
= (options
.our_label
) ?
97 options
.our_label
: ours
->path
;
98 our_mmfile
.ptr
= (char *)ours
->ptr
;
99 our_mmfile
.size
= ours
->size
;
101 xmparam
.file2
= (options
.their_label
) ?
102 options
.their_label
: theirs
->path
;
103 their_mmfile
.ptr
= (char *)theirs
->ptr
;
104 their_mmfile
.size
= theirs
->size
;
106 if (options
.favor
== GIT_MERGE_FILE_FAVOR_OURS
)
107 xmparam
.favor
= XDL_MERGE_FAVOR_OURS
;
108 else if (options
.favor
== GIT_MERGE_FILE_FAVOR_THEIRS
)
109 xmparam
.favor
= XDL_MERGE_FAVOR_THEIRS
;
110 else if (options
.favor
== GIT_MERGE_FILE_FAVOR_UNION
)
111 xmparam
.favor
= XDL_MERGE_FAVOR_UNION
;
113 xmparam
.level
= (options
.flags
& GIT_MERGE_FILE_SIMPLIFY_ALNUM
) ?
114 XDL_MERGE_ZEALOUS_ALNUM
: XDL_MERGE_ZEALOUS
;
116 if (options
.flags
& GIT_MERGE_FILE_STYLE_DIFF3
)
117 xmparam
.style
= XDL_MERGE_DIFF3
;
119 if (options
.flags
& GIT_MERGE_FILE_IGNORE_WHITESPACE
)
120 xmparam
.xpp
.flags
|= XDF_IGNORE_WHITESPACE
;
121 if (options
.flags
& GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE
)
122 xmparam
.xpp
.flags
|= XDF_IGNORE_WHITESPACE_CHANGE
;
123 if (options
.flags
& GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL
)
124 xmparam
.xpp
.flags
|= XDF_IGNORE_WHITESPACE_AT_EOL
;
126 if (options
.flags
& GIT_MERGE_FILE_DIFF_PATIENCE
)
127 xmparam
.xpp
.flags
|= XDF_PATIENCE_DIFF
;
129 if (options
.flags
& GIT_MERGE_FILE_DIFF_MINIMAL
)
130 xmparam
.xpp
.flags
|= XDF_NEED_MINIMAL
;
132 xmparam
.marker_size
= options
.marker_size
;
134 if ((xdl_result
= xdl_merge(&ancestor_mmfile
, &our_mmfile
,
135 &their_mmfile
, &xmparam
, &mmbuffer
)) < 0) {
136 git_error_set(GIT_ERROR_MERGE
, "failed to merge files");
141 path
= git_merge_file__best_path(
142 ancestor
? ancestor
->path
: NULL
,
146 if (path
!= NULL
&& (out
->path
= git__strdup(path
)) == NULL
) {
151 out
->automergeable
= (xdl_result
== 0);
152 out
->ptr
= (const char *)mmbuffer
.ptr
;
153 out
->len
= mmbuffer
.size
;
154 out
->mode
= git_merge_file__best_mode(
155 ancestor
? ancestor
->mode
: 0,
161 git_merge_file_result_free(out
);
166 static bool merge_file__is_binary(const git_merge_file_input
*file
)
168 size_t len
= file
? file
->size
: 0;
170 if (len
> GIT_XDIFF_MAX_SIZE
)
172 if (len
> GIT_MERGE_FILE_BINARY_SIZE
)
173 len
= GIT_MERGE_FILE_BINARY_SIZE
;
175 return len
? (memchr(file
->ptr
, 0, len
) != NULL
) : false;
178 static int merge_file__binary(
179 git_merge_file_result
*out
,
180 const git_merge_file_input
*ours
,
181 const git_merge_file_input
*theirs
,
182 const git_merge_file_options
*given_opts
)
184 const git_merge_file_input
*favored
= NULL
;
186 memset(out
, 0x0, sizeof(git_merge_file_result
));
188 if (given_opts
&& given_opts
->favor
== GIT_MERGE_FILE_FAVOR_OURS
)
190 else if (given_opts
&& given_opts
->favor
== GIT_MERGE_FILE_FAVOR_THEIRS
)
195 if ((out
->path
= git__strdup(favored
->path
)) == NULL
||
196 (out
->ptr
= git__malloc(favored
->size
)) == NULL
)
199 memcpy((char *)out
->ptr
, favored
->ptr
, favored
->size
);
200 out
->len
= favored
->size
;
201 out
->mode
= favored
->mode
;
202 out
->automergeable
= 1;
208 static int merge_file__from_inputs(
209 git_merge_file_result
*out
,
210 const git_merge_file_input
*ancestor
,
211 const git_merge_file_input
*ours
,
212 const git_merge_file_input
*theirs
,
213 const git_merge_file_options
*given_opts
)
215 if (merge_file__is_binary(ancestor
) ||
216 merge_file__is_binary(ours
) ||
217 merge_file__is_binary(theirs
))
218 return merge_file__binary(out
, ours
, theirs
, given_opts
);
220 return merge_file__xdiff(out
, ancestor
, ours
, theirs
, given_opts
);
223 static git_merge_file_input
*git_merge_file__normalize_inputs(
224 git_merge_file_input
*out
,
225 const git_merge_file_input
*given
)
227 memcpy(out
, given
, sizeof(git_merge_file_input
));
230 out
->path
= "file.txt";
239 git_merge_file_result
*out
,
240 const git_merge_file_input
*ancestor
,
241 const git_merge_file_input
*ours
,
242 const git_merge_file_input
*theirs
,
243 const git_merge_file_options
*options
)
245 git_merge_file_input inputs
[3] = { {0} };
248 GIT_ASSERT_ARG(ours
);
249 GIT_ASSERT_ARG(theirs
);
251 memset(out
, 0x0, sizeof(git_merge_file_result
));
254 ancestor
= git_merge_file__normalize_inputs(&inputs
[0], ancestor
);
256 ours
= git_merge_file__normalize_inputs(&inputs
[1], ours
);
257 theirs
= git_merge_file__normalize_inputs(&inputs
[2], theirs
);
259 return merge_file__from_inputs(out
, ancestor
, ours
, theirs
, options
);
262 int git_merge_file_from_index(
263 git_merge_file_result
*out
,
264 git_repository
*repo
,
265 const git_index_entry
*ancestor
,
266 const git_index_entry
*ours
,
267 const git_index_entry
*theirs
,
268 const git_merge_file_options
*options
)
270 git_merge_file_input
*ancestor_ptr
= NULL
,
271 ancestor_input
= {0}, our_input
= {0}, their_input
= {0};
273 git_odb_object
*odb_object
[3] = { 0 };
277 GIT_ASSERT_ARG(repo
);
278 GIT_ASSERT_ARG(ours
);
279 GIT_ASSERT_ARG(theirs
);
281 memset(out
, 0x0, sizeof(git_merge_file_result
));
283 if ((error
= git_repository_odb(&odb
, repo
)) < 0)
287 if ((error
= merge_file_input_from_index(
288 &ancestor_input
, &odb_object
[0], odb
, ancestor
)) < 0)
291 ancestor_ptr
= &ancestor_input
;
294 if ((error
= merge_file_input_from_index(&our_input
, &odb_object
[1], odb
, ours
)) < 0 ||
295 (error
= merge_file_input_from_index(&their_input
, &odb_object
[2], odb
, theirs
)) < 0)
298 error
= merge_file__from_inputs(out
,
299 ancestor_ptr
, &our_input
, &their_input
, options
);
302 git_odb_object_free(odb_object
[0]);
303 git_odb_object_free(odb_object
[1]);
304 git_odb_object_free(odb_object
[2]);
310 void git_merge_file_result_free(git_merge_file_result
*result
)
315 git__free((char *)result
->path
);
316 git__free((char *)result
->ptr
);