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 assert(input_out
&& odb_object_out
&& odb
&& entry
);
41 if ((error
= git_odb_read(odb_object_out
, odb
, &entry
->id
)) < 0)
44 input_out
->path
= entry
->path
;
45 input_out
->mode
= entry
->mode
;
46 input_out
->ptr
= (char *)git_odb_object_data(*odb_object_out
);
47 input_out
->size
= git_odb_object_size(*odb_object_out
);
53 static void merge_file_normalize_opts(
54 git_merge_file_options
*out
,
55 const git_merge_file_options
*given_opts
)
58 memcpy(out
, given_opts
, sizeof(git_merge_file_options
));
60 git_merge_file_options default_opts
= GIT_MERGE_FILE_OPTIONS_INIT
;
61 memcpy(out
, &default_opts
, sizeof(git_merge_file_options
));
65 static int merge_file__xdiff(
66 git_merge_file_result
*out
,
67 const git_merge_file_input
*ancestor
,
68 const git_merge_file_input
*ours
,
69 const git_merge_file_input
*theirs
,
70 const git_merge_file_options
*given_opts
)
73 mmfile_t ancestor_mmfile
= {0}, our_mmfile
= {0}, their_mmfile
= {0};
75 git_merge_file_options options
= GIT_MERGE_FILE_OPTIONS_INIT
;
80 memset(out
, 0x0, sizeof(git_merge_file_result
));
82 merge_file_normalize_opts(&options
, given_opts
);
84 memset(&xmparam
, 0x0, sizeof(xmparam_t
));
87 xmparam
.ancestor
= (options
.ancestor_label
) ?
88 options
.ancestor_label
: ancestor
->path
;
89 ancestor_mmfile
.ptr
= (char *)ancestor
->ptr
;
90 ancestor_mmfile
.size
= ancestor
->size
;
93 xmparam
.file1
= (options
.our_label
) ?
94 options
.our_label
: ours
->path
;
95 our_mmfile
.ptr
= (char *)ours
->ptr
;
96 our_mmfile
.size
= ours
->size
;
98 xmparam
.file2
= (options
.their_label
) ?
99 options
.their_label
: theirs
->path
;
100 their_mmfile
.ptr
= (char *)theirs
->ptr
;
101 their_mmfile
.size
= theirs
->size
;
103 if (options
.favor
== GIT_MERGE_FILE_FAVOR_OURS
)
104 xmparam
.favor
= XDL_MERGE_FAVOR_OURS
;
105 else if (options
.favor
== GIT_MERGE_FILE_FAVOR_THEIRS
)
106 xmparam
.favor
= XDL_MERGE_FAVOR_THEIRS
;
107 else if (options
.favor
== GIT_MERGE_FILE_FAVOR_UNION
)
108 xmparam
.favor
= XDL_MERGE_FAVOR_UNION
;
110 xmparam
.level
= (options
.flags
& GIT_MERGE_FILE_SIMPLIFY_ALNUM
) ?
111 XDL_MERGE_ZEALOUS_ALNUM
: XDL_MERGE_ZEALOUS
;
113 if (options
.flags
& GIT_MERGE_FILE_STYLE_DIFF3
)
114 xmparam
.style
= XDL_MERGE_DIFF3
;
116 if (options
.flags
& GIT_MERGE_FILE_IGNORE_WHITESPACE
)
117 xmparam
.xpp
.flags
|= XDF_IGNORE_WHITESPACE
;
118 if (options
.flags
& GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE
)
119 xmparam
.xpp
.flags
|= XDF_IGNORE_WHITESPACE_CHANGE
;
120 if (options
.flags
& GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL
)
121 xmparam
.xpp
.flags
|= XDF_IGNORE_WHITESPACE_AT_EOL
;
123 if (options
.flags
& GIT_MERGE_FILE_DIFF_PATIENCE
)
124 xmparam
.xpp
.flags
|= XDF_PATIENCE_DIFF
;
126 if (options
.flags
& GIT_MERGE_FILE_DIFF_MINIMAL
)
127 xmparam
.xpp
.flags
|= XDF_NEED_MINIMAL
;
129 xmparam
.marker_size
= options
.marker_size
;
131 if ((xdl_result
= xdl_merge(&ancestor_mmfile
, &our_mmfile
,
132 &their_mmfile
, &xmparam
, &mmbuffer
)) < 0) {
133 git_error_set(GIT_ERROR_MERGE
, "failed to merge files");
138 path
= git_merge_file__best_path(
139 ancestor
? ancestor
->path
: NULL
,
143 if (path
!= NULL
&& (out
->path
= git__strdup(path
)) == NULL
) {
148 out
->automergeable
= (xdl_result
== 0);
149 out
->ptr
= (const char *)mmbuffer
.ptr
;
150 out
->len
= mmbuffer
.size
;
151 out
->mode
= git_merge_file__best_mode(
152 ancestor
? ancestor
->mode
: 0,
158 git_merge_file_result_free(out
);
163 static bool merge_file__is_binary(const git_merge_file_input
*file
)
165 size_t len
= file
? file
->size
: 0;
167 if (len
> GIT_XDIFF_MAX_SIZE
)
169 if (len
> GIT_MERGE_FILE_BINARY_SIZE
)
170 len
= GIT_MERGE_FILE_BINARY_SIZE
;
172 return len
? (memchr(file
->ptr
, 0, len
) != NULL
) : false;
175 static int merge_file__binary(
176 git_merge_file_result
*out
,
177 const git_merge_file_input
*ours
,
178 const git_merge_file_input
*theirs
,
179 const git_merge_file_options
*given_opts
)
181 const git_merge_file_input
*favored
= NULL
;
183 memset(out
, 0x0, sizeof(git_merge_file_result
));
185 if (given_opts
&& given_opts
->favor
== GIT_MERGE_FILE_FAVOR_OURS
)
187 else if (given_opts
&& given_opts
->favor
== GIT_MERGE_FILE_FAVOR_THEIRS
)
192 if ((out
->path
= git__strdup(favored
->path
)) == NULL
||
193 (out
->ptr
= git__malloc(favored
->size
)) == NULL
)
196 memcpy((char *)out
->ptr
, favored
->ptr
, favored
->size
);
197 out
->len
= favored
->size
;
198 out
->mode
= favored
->mode
;
199 out
->automergeable
= 1;
205 static int merge_file__from_inputs(
206 git_merge_file_result
*out
,
207 const git_merge_file_input
*ancestor
,
208 const git_merge_file_input
*ours
,
209 const git_merge_file_input
*theirs
,
210 const git_merge_file_options
*given_opts
)
212 if (merge_file__is_binary(ancestor
) ||
213 merge_file__is_binary(ours
) ||
214 merge_file__is_binary(theirs
))
215 return merge_file__binary(out
, ours
, theirs
, given_opts
);
217 return merge_file__xdiff(out
, ancestor
, ours
, theirs
, given_opts
);
220 static git_merge_file_input
*git_merge_file__normalize_inputs(
221 git_merge_file_input
*out
,
222 const git_merge_file_input
*given
)
224 memcpy(out
, given
, sizeof(git_merge_file_input
));
227 out
->path
= "file.txt";
236 git_merge_file_result
*out
,
237 const git_merge_file_input
*ancestor
,
238 const git_merge_file_input
*ours
,
239 const git_merge_file_input
*theirs
,
240 const git_merge_file_options
*options
)
242 git_merge_file_input inputs
[3] = { {0} };
244 assert(out
&& ours
&& theirs
);
246 memset(out
, 0x0, sizeof(git_merge_file_result
));
249 ancestor
= git_merge_file__normalize_inputs(&inputs
[0], ancestor
);
251 ours
= git_merge_file__normalize_inputs(&inputs
[1], ours
);
252 theirs
= git_merge_file__normalize_inputs(&inputs
[2], theirs
);
254 return merge_file__from_inputs(out
, ancestor
, ours
, theirs
, options
);
257 int git_merge_file_from_index(
258 git_merge_file_result
*out
,
259 git_repository
*repo
,
260 const git_index_entry
*ancestor
,
261 const git_index_entry
*ours
,
262 const git_index_entry
*theirs
,
263 const git_merge_file_options
*options
)
265 git_merge_file_input
*ancestor_ptr
= NULL
,
266 ancestor_input
= {0}, our_input
= {0}, their_input
= {0};
268 git_odb_object
*odb_object
[3] = { 0 };
271 assert(out
&& repo
&& ours
&& theirs
);
273 memset(out
, 0x0, sizeof(git_merge_file_result
));
275 if ((error
= git_repository_odb(&odb
, repo
)) < 0)
279 if ((error
= merge_file_input_from_index(
280 &ancestor_input
, &odb_object
[0], odb
, ancestor
)) < 0)
283 ancestor_ptr
= &ancestor_input
;
286 if ((error
= merge_file_input_from_index(&our_input
, &odb_object
[1], odb
, ours
)) < 0 ||
287 (error
= merge_file_input_from_index(&their_input
, &odb_object
[2], odb
, theirs
)) < 0)
290 error
= merge_file__from_inputs(out
,
291 ancestor_ptr
, &our_input
, &their_input
, options
);
294 git_odb_object_free(odb_object
[0]);
295 git_odb_object_free(odb_object
[1]);
296 git_odb_object_free(odb_object
[2]);
302 void git_merge_file_result_free(git_merge_file_result
*result
)
307 git__free((char *)result
->path
);
308 git__free((char *)result
->ptr
);