]> git.proxmox.com Git - libgit2.git/blob - src/merge_file.c
New upstream version 1.3.0+dfsg.1
[libgit2.git] / src / merge_file.c
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
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
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
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
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
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
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
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
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
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
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 }