1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
3  *
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.
6  */
7 
8 #include "common.h"
9 
10 #include "repository.h"
11 #include "posix.h"
12 #include "futils.h"
13 #include "index.h"
14 #include "diff_xdiff.h"
15 #include "merge.h"
16 
17 #include "git2/repository.h"
18 #include "git2/object.h"
19 #include "git2/index.h"
20 #include "git2/merge.h"
21 
22 #include "xdiff/xdiff.h"
23 
24 /* only examine the first 8000 bytes for binaryness.
25  * https://github.com/git/git/blob/77bd3ea9f54f1584147b594abc04c26ca516d987/xdiff-interface.c#L197
26  */
27 #define GIT_MERGE_FILE_BINARY_SIZE 8000
28 
29 #define GIT_MERGE_FILE_SIDE_EXISTS(X)	((X)->mode != 0)
30 
merge_file_input_from_index(git_merge_file_input * input_out,git_odb_object ** odb_object_out,git_odb * odb,const git_index_entry * entry)31 static int merge_file_input_from_index(
32 	git_merge_file_input *input_out,
33 	git_odb_object **odb_object_out,
34 	git_odb *odb,
35 	const git_index_entry *entry)
36 {
37 	int error = 0;
38 
39 	GIT_ASSERT_ARG(input_out);
40 	GIT_ASSERT_ARG(odb_object_out);
41 	GIT_ASSERT_ARG(odb);
42 	GIT_ASSERT_ARG(entry);
43 
44 	if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0)
45 		goto done;
46 
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);
51 
52 done:
53 	return error;
54 }
55 
merge_file_normalize_opts(git_merge_file_options * out,const git_merge_file_options * given_opts)56 static void merge_file_normalize_opts(
57 	git_merge_file_options *out,
58 	const git_merge_file_options *given_opts)
59 {
60 	if (given_opts)
61 		memcpy(out, given_opts, sizeof(git_merge_file_options));
62 	else {
63 		git_merge_file_options default_opts = GIT_MERGE_FILE_OPTIONS_INIT;
64 		memcpy(out, &default_opts, sizeof(git_merge_file_options));
65 	}
66 }
67 
merge_file__xdiff(git_merge_file_result * out,const git_merge_file_input * ancestor,const git_merge_file_input * ours,const git_merge_file_input * theirs,const git_merge_file_options * given_opts)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)
74 {
75 	xmparam_t xmparam;
76 	mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0};
77 	mmbuffer_t mmbuffer;
78 	git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT;
79 	const char *path;
80 	int xdl_result;
81 	int error = 0;
82 
83 	memset(out, 0x0, sizeof(git_merge_file_result));
84 
85 	merge_file_normalize_opts(&options, given_opts);
86 
87 	memset(&xmparam, 0x0, sizeof(xmparam_t));
88 
89 	if (ancestor) {
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;
94 	}
95 
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;
100 
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;
105 
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;
112 
113 	xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ?
114 		XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS;
115 
116 	if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3)
117 		xmparam.style = XDL_MERGE_DIFF3;
118 
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;
125 
126 	if (options.flags & GIT_MERGE_FILE_DIFF_PATIENCE)
127 		xmparam.xpp.flags |= XDF_PATIENCE_DIFF;
128 
129 	if (options.flags & GIT_MERGE_FILE_DIFF_MINIMAL)
130 		xmparam.xpp.flags |= XDF_NEED_MINIMAL;
131 
132 	xmparam.marker_size = options.marker_size;
133 
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");
137 		error = -1;
138 		goto done;
139 	}
140 
141 	path = git_merge_file__best_path(
142 		ancestor ? ancestor->path : NULL,
143 		ours->path,
144 		theirs->path);
145 
146 	if (path != NULL && (out->path = git__strdup(path)) == NULL) {
147 		error = -1;
148 		goto done;
149 	}
150 
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,
156 		ours->mode,
157 		theirs->mode);
158 
159 done:
160 	if (error < 0)
161 		git_merge_file_result_free(out);
162 
163 	return error;
164 }
165 
merge_file__is_binary(const git_merge_file_input * file)166 static bool merge_file__is_binary(const git_merge_file_input *file)
167 {
168 	size_t len = file ? file->size : 0;
169 
170 	if (len > GIT_XDIFF_MAX_SIZE)
171 		return true;
172 	if (len > GIT_MERGE_FILE_BINARY_SIZE)
173 		len = GIT_MERGE_FILE_BINARY_SIZE;
174 
175 	return len ? (memchr(file->ptr, 0, len) != NULL) : false;
176 }
177 
merge_file__binary(git_merge_file_result * out,const git_merge_file_input * ours,const git_merge_file_input * theirs,const git_merge_file_options * given_opts)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)
183 {
184 	const git_merge_file_input *favored = NULL;
185 
186 	memset(out, 0x0, sizeof(git_merge_file_result));
187 
188 	if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_OURS)
189 		favored = ours;
190 	else if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_THEIRS)
191 		favored = theirs;
192 	else
193 		goto done;
194 
195 	if ((out->path = git__strdup(favored->path)) == NULL ||
196 		(out->ptr = git__malloc(favored->size)) == NULL)
197 		goto done;
198 
199 	memcpy((char *)out->ptr, favored->ptr, favored->size);
200 	out->len = favored->size;
201 	out->mode = favored->mode;
202 	out->automergeable = 1;
203 
204 done:
205 	return 0;
206 }
207 
merge_file__from_inputs(git_merge_file_result * out,const git_merge_file_input * ancestor,const git_merge_file_input * ours,const git_merge_file_input * theirs,const git_merge_file_options * given_opts)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)
214 {
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);
219 
220 	return merge_file__xdiff(out, ancestor, ours, theirs, given_opts);
221 }
222 
git_merge_file__normalize_inputs(git_merge_file_input * out,const git_merge_file_input * given)223 static git_merge_file_input *git_merge_file__normalize_inputs(
224 	git_merge_file_input *out,
225 	const git_merge_file_input *given)
226 {
227 	memcpy(out, given, sizeof(git_merge_file_input));
228 
229 	if (!out->path)
230 		out->path = "file.txt";
231 
232 	if (!out->mode)
233 		out->mode = 0100644;
234 
235 	return out;
236 }
237 
git_merge_file(git_merge_file_result * out,const git_merge_file_input * ancestor,const git_merge_file_input * ours,const git_merge_file_input * theirs,const git_merge_file_options * options)238 int git_merge_file(
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)
244 {
245 	git_merge_file_input inputs[3] = { {0} };
246 
247 	GIT_ASSERT_ARG(out);
248 	GIT_ASSERT_ARG(ours);
249 	GIT_ASSERT_ARG(theirs);
250 
251 	memset(out, 0x0, sizeof(git_merge_file_result));
252 
253 	if (ancestor)
254 		ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor);
255 
256 	ours = git_merge_file__normalize_inputs(&inputs[1], ours);
257 	theirs = git_merge_file__normalize_inputs(&inputs[2], theirs);
258 
259 	return merge_file__from_inputs(out, ancestor, ours, theirs, options);
260 }
261 
git_merge_file_from_index(git_merge_file_result * out,git_repository * repo,const git_index_entry * ancestor,const git_index_entry * ours,const git_index_entry * theirs,const git_merge_file_options * 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)
269 {
270 	git_merge_file_input *ancestor_ptr = NULL,
271 		ancestor_input = {0}, our_input = {0}, their_input = {0};
272 	git_odb *odb = NULL;
273 	git_odb_object *odb_object[3] = { 0 };
274 	int error = 0;
275 
276 	GIT_ASSERT_ARG(out);
277 	GIT_ASSERT_ARG(repo);
278 	GIT_ASSERT_ARG(ours);
279 	GIT_ASSERT_ARG(theirs);
280 
281 	memset(out, 0x0, sizeof(git_merge_file_result));
282 
283 	if ((error = git_repository_odb(&odb, repo)) < 0)
284 		goto done;
285 
286 	if (ancestor) {
287 		if ((error = merge_file_input_from_index(
288 			&ancestor_input, &odb_object[0], odb, ancestor)) < 0)
289 			goto done;
290 
291 		ancestor_ptr = &ancestor_input;
292 	}
293 
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)
296 		goto done;
297 
298 	error = merge_file__from_inputs(out,
299 		ancestor_ptr, &our_input, &their_input, options);
300 
301 done:
302 	git_odb_object_free(odb_object[0]);
303 	git_odb_object_free(odb_object[1]);
304 	git_odb_object_free(odb_object[2]);
305 	git_odb_free(odb);
306 
307 	return error;
308 }
309 
git_merge_file_result_free(git_merge_file_result * result)310 void git_merge_file_result_free(git_merge_file_result *result)
311 {
312 	if (result == NULL)
313 		return;
314 
315 	git__free((char *)result->path);
316 	git__free((char *)result->ptr);
317 }
318