File Coverage

deps/libgit2/src/libgit2/merge_file.c
Criterion Covered Total %
statement 100 158 63.2
branch 52 114 45.6
condition n/a
subroutine n/a
pod n/a
total 152 272 55.8


line stmt bran cond sub pod time code
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 33           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 33           int error = 0;
38              
39 33 50         GIT_ASSERT_ARG(input_out);
40 33 50         GIT_ASSERT_ARG(odb_object_out);
41 33 50         GIT_ASSERT_ARG(odb);
42 33 50         GIT_ASSERT_ARG(entry);
43              
44 33 50         if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0)
45 0           goto done;
46              
47 33           input_out->path = entry->path;
48 33           input_out->mode = entry->mode;
49 33           input_out->ptr = (char *)git_odb_object_data(*odb_object_out);
50 33           input_out->size = git_odb_object_size(*odb_object_out);
51              
52             done:
53 33           return error;
54             }
55              
56 11           static void merge_file_normalize_opts(
57             git_merge_file_options *out,
58             const git_merge_file_options *given_opts)
59             {
60 11 50         if (given_opts)
61 11           memcpy(out, given_opts, sizeof(git_merge_file_options));
62             else {
63 0           git_merge_file_options default_opts = GIT_MERGE_FILE_OPTIONS_INIT;
64 0           memcpy(out, &default_opts, sizeof(git_merge_file_options));
65             }
66 11           }
67              
68 11           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 11           mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0};
77             mmbuffer_t mmbuffer;
78 11           git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT;
79             const char *path;
80             int xdl_result;
81 11           int error = 0;
82              
83 11           memset(out, 0x0, sizeof(git_merge_file_result));
84              
85 11           merge_file_normalize_opts(&options, given_opts);
86              
87 11           memset(&xmparam, 0x0, sizeof(xmparam_t));
88              
89 11 50         if (ours->size > LONG_MAX ||
    50          
90 11 50         theirs->size > LONG_MAX ||
91 11 50         (ancestor && ancestor->size > LONG_MAX)) {
92 0           git_error_set(GIT_ERROR_MERGE, "failed to merge files");
93 0           error = -1;
94 0           goto done;
95             }
96              
97 11 50         if (ancestor) {
98 22           xmparam.ancestor = (options.ancestor_label) ?
99 11 100         options.ancestor_label : ancestor->path;
100 11           ancestor_mmfile.ptr = (char *)ancestor->ptr;
101 11           ancestor_mmfile.size = (long)ancestor->size;
102             }
103              
104 22           xmparam.file1 = (options.our_label) ?
105 11 100         options.our_label : ours->path;
106 11           our_mmfile.ptr = (char *)ours->ptr;
107 11           our_mmfile.size = (long)ours->size;
108              
109 22           xmparam.file2 = (options.their_label) ?
110 11 100         options.their_label : theirs->path;
111 11           their_mmfile.ptr = (char *)theirs->ptr;
112 11           their_mmfile.size = (long)theirs->size;
113              
114 11 100         if (options.favor == GIT_MERGE_FILE_FAVOR_OURS)
115 2           xmparam.favor = XDL_MERGE_FAVOR_OURS;
116 9 100         else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS)
117 1           xmparam.favor = XDL_MERGE_FAVOR_THEIRS;
118 8 50         else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION)
119 0           xmparam.favor = XDL_MERGE_FAVOR_UNION;
120              
121 22           xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ?
122 11 50         XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS;
123              
124 11 50         if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3)
125 0           xmparam.style = XDL_MERGE_DIFF3;
126 11 50         if (options.flags & GIT_MERGE_FILE_STYLE_ZDIFF3)
127 0           xmparam.style = XDL_MERGE_ZEALOUS_DIFF3;
128              
129 11 50         if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE)
130 0           xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE;
131 11 50         if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE)
132 0           xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
133 11 50         if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL)
134 0           xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
135              
136 11 50         if (options.flags & GIT_MERGE_FILE_DIFF_PATIENCE)
137 0           xmparam.xpp.flags |= XDF_PATIENCE_DIFF;
138              
139 11 50         if (options.flags & GIT_MERGE_FILE_DIFF_MINIMAL)
140 0           xmparam.xpp.flags |= XDF_NEED_MINIMAL;
141              
142 11           xmparam.marker_size = options.marker_size;
143              
144 11 50         if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile,
145             &their_mmfile, &xmparam, &mmbuffer)) < 0) {
146 0           git_error_set(GIT_ERROR_MERGE, "failed to merge files");
147 0           error = -1;
148 0           goto done;
149             }
150              
151 11 50         path = git_merge_file__best_path(
152             ancestor ? ancestor->path : NULL,
153             ours->path,
154             theirs->path);
155              
156 11 50         if (path != NULL && (out->path = git__strdup(path)) == NULL) {
    50          
157 0           error = -1;
158 0           goto done;
159             }
160              
161 11           out->automergeable = (xdl_result == 0);
162 11           out->ptr = (const char *)mmbuffer.ptr;
163 11           out->len = mmbuffer.size;
164 11 50         out->mode = git_merge_file__best_mode(
165             ancestor ? ancestor->mode : 0,
166             ours->mode,
167             theirs->mode);
168              
169             done:
170 11 50         if (error < 0)
171 0           git_merge_file_result_free(out);
172              
173 11           return error;
174             }
175              
176 33           static bool merge_file__is_binary(const git_merge_file_input *file)
177             {
178 33 50         size_t len = file ? file->size : 0;
179              
180 33 50         if (len > GIT_XDIFF_MAX_SIZE)
181 0           return true;
182 33 50         if (len > GIT_MERGE_FILE_BINARY_SIZE)
183 0           len = GIT_MERGE_FILE_BINARY_SIZE;
184              
185 33 50         return len ? (memchr(file->ptr, 0, len) != NULL) : false;
    50          
186             }
187              
188 0           static int merge_file__binary(
189             git_merge_file_result *out,
190             const git_merge_file_input *ours,
191             const git_merge_file_input *theirs,
192             const git_merge_file_options *given_opts)
193             {
194 0           const git_merge_file_input *favored = NULL;
195              
196 0           memset(out, 0x0, sizeof(git_merge_file_result));
197              
198 0 0         if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_OURS)
    0          
199 0           favored = ours;
200 0 0         else if (given_opts && given_opts->favor == GIT_MERGE_FILE_FAVOR_THEIRS)
    0          
201 0           favored = theirs;
202             else
203             goto done;
204              
205 0           if ((out->path = git__strdup(favored->path)) == NULL ||
206 0           (out->ptr = git__malloc(favored->size)) == NULL)
207             goto done;
208              
209 0           memcpy((char *)out->ptr, favored->ptr, favored->size);
210 0           out->len = favored->size;
211 0           out->mode = favored->mode;
212 0           out->automergeable = 1;
213              
214             done:
215 0           return 0;
216             }
217              
218 11           static int merge_file__from_inputs(
219             git_merge_file_result *out,
220             const git_merge_file_input *ancestor,
221             const git_merge_file_input *ours,
222             const git_merge_file_input *theirs,
223             const git_merge_file_options *given_opts)
224             {
225 22           if (merge_file__is_binary(ancestor) ||
226 22 50         merge_file__is_binary(ours) ||
227 11           merge_file__is_binary(theirs))
228 0           return merge_file__binary(out, ours, theirs, given_opts);
229              
230 11           return merge_file__xdiff(out, ancestor, ours, theirs, given_opts);
231             }
232              
233 0           static git_merge_file_input *git_merge_file__normalize_inputs(
234             git_merge_file_input *out,
235             const git_merge_file_input *given)
236             {
237 0           memcpy(out, given, sizeof(git_merge_file_input));
238              
239 0 0         if (!out->path)
240 0           out->path = "file.txt";
241              
242 0 0         if (!out->mode)
243 0           out->mode = 0100644;
244              
245 0           return out;
246             }
247              
248 0           int git_merge_file(
249             git_merge_file_result *out,
250             const git_merge_file_input *ancestor,
251             const git_merge_file_input *ours,
252             const git_merge_file_input *theirs,
253             const git_merge_file_options *options)
254             {
255 0           git_merge_file_input inputs[3] = { {0} };
256              
257 0 0         GIT_ASSERT_ARG(out);
258 0 0         GIT_ASSERT_ARG(ours);
259 0 0         GIT_ASSERT_ARG(theirs);
260              
261 0           memset(out, 0x0, sizeof(git_merge_file_result));
262              
263 0 0         if (ancestor)
264 0           ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor);
265              
266 0           ours = git_merge_file__normalize_inputs(&inputs[1], ours);
267 0           theirs = git_merge_file__normalize_inputs(&inputs[2], theirs);
268              
269 0           return merge_file__from_inputs(out, ancestor, ours, theirs, options);
270             }
271              
272 11           int git_merge_file_from_index(
273             git_merge_file_result *out,
274             git_repository *repo,
275             const git_index_entry *ancestor,
276             const git_index_entry *ours,
277             const git_index_entry *theirs,
278             const git_merge_file_options *options)
279             {
280 11           git_merge_file_input *ancestor_ptr = NULL,
281 11           ancestor_input = {0}, our_input = {0}, their_input = {0};
282 11           git_odb *odb = NULL;
283 11           git_odb_object *odb_object[3] = { 0 };
284 11           int error = 0;
285              
286 11 50         GIT_ASSERT_ARG(out);
287 11 50         GIT_ASSERT_ARG(repo);
288 11 50         GIT_ASSERT_ARG(ours);
289 11 50         GIT_ASSERT_ARG(theirs);
290              
291 11           memset(out, 0x0, sizeof(git_merge_file_result));
292              
293 11 50         if ((error = git_repository_odb(&odb, repo)) < 0)
294 0           goto done;
295              
296 11 50         if (ancestor) {
297 11 50         if ((error = merge_file_input_from_index(
298             &ancestor_input, &odb_object[0], odb, ancestor)) < 0)
299 0           goto done;
300              
301 11           ancestor_ptr = &ancestor_input;
302             }
303              
304 11 50         if ((error = merge_file_input_from_index(&our_input, &odb_object[1], odb, ours)) < 0 ||
    50          
305 11           (error = merge_file_input_from_index(&their_input, &odb_object[2], odb, theirs)) < 0)
306             goto done;
307              
308 11           error = merge_file__from_inputs(out,
309             ancestor_ptr, &our_input, &their_input, options);
310              
311             done:
312 11           git_odb_object_free(odb_object[0]);
313 11           git_odb_object_free(odb_object[1]);
314 11           git_odb_object_free(odb_object[2]);
315 11           git_odb_free(odb);
316              
317 11           return error;
318             }
319              
320 17           void git_merge_file_result_free(git_merge_file_result *result)
321             {
322 17 50         if (result == NULL)
323 0           return;
324              
325 17           git__free((char *)result->path);
326 17           git__free((char *)result->ptr);
327             }