]> git.proxmox.com Git - libgit2.git/blob - src/merge_file.c
Merge pull request #2178 from libgit2/rb/fix-short-id
[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 #include "repository.h"
10 #include "merge_file.h"
11 #include "posix.h"
12 #include "fileops.h"
13 #include "index.h"
14
15 #include "git2/repository.h"
16 #include "git2/object.h"
17 #include "git2/index.h"
18
19 #include "xdiff/xdiff.h"
20
21 #define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0)
22
23 GIT_INLINE(const char *) merge_file_best_path(
24 const git_merge_file_input *ancestor,
25 const git_merge_file_input *ours,
26 const git_merge_file_input *theirs)
27 {
28 if (!ancestor) {
29 if (ours && theirs && strcmp(ours->path, theirs->path) == 0)
30 return ours->path;
31
32 return NULL;
33 }
34
35 if (ours && strcmp(ancestor->path, ours->path) == 0)
36 return theirs ? theirs->path : NULL;
37 else if(theirs && strcmp(ancestor->path, theirs->path) == 0)
38 return ours ? ours->path : NULL;
39
40 return NULL;
41 }
42
43 GIT_INLINE(int) merge_file_best_mode(
44 const git_merge_file_input *ancestor,
45 const git_merge_file_input *ours,
46 const git_merge_file_input *theirs)
47 {
48 /*
49 * If ancestor didn't exist and either ours or theirs is executable,
50 * assume executable. Otherwise, if any mode changed from the ancestor,
51 * use that one.
52 */
53 if (!ancestor) {
54 if ((ours && ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE) ||
55 (theirs && theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE))
56 return GIT_FILEMODE_BLOB_EXECUTABLE;
57
58 return GIT_FILEMODE_BLOB;
59 } else if (ours && theirs) {
60 if (ancestor->mode == ours->mode)
61 return theirs->mode;
62
63 return ours->mode;
64 }
65
66 return 0;
67 }
68
69 int git_merge_file__input_from_index(
70 git_merge_file_input *input_out,
71 git_odb_object **odb_object_out,
72 git_odb *odb,
73 const git_index_entry *entry)
74 {
75 int error = 0;
76
77 assert(input_out && odb_object_out && odb && entry);
78
79 if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0)
80 goto done;
81
82 input_out->path = entry->path;
83 input_out->mode = entry->mode;
84 input_out->ptr = (char *)git_odb_object_data(*odb_object_out);
85 input_out->size = git_odb_object_size(*odb_object_out);
86
87 done:
88 return error;
89 }
90
91 static void merge_file_normalize_opts(
92 git_merge_file_options *out,
93 const git_merge_file_options *given_opts)
94 {
95 if (given_opts)
96 memcpy(out, given_opts, sizeof(git_merge_file_options));
97 else {
98 git_merge_file_options default_opts = GIT_MERGE_FILE_OPTIONS_INIT;
99 memcpy(out, &default_opts, sizeof(git_merge_file_options));
100 }
101 }
102
103 static int git_merge_file__from_inputs(
104 git_merge_file_result *out,
105 const git_merge_file_input *ancestor,
106 const git_merge_file_input *ours,
107 const git_merge_file_input *theirs,
108 const git_merge_file_options *given_opts)
109 {
110 xmparam_t xmparam;
111 mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0};
112 mmbuffer_t mmbuffer;
113 git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT;
114 const char *path;
115 int xdl_result;
116 int error = 0;
117
118 memset(out, 0x0, sizeof(git_merge_file_result));
119
120 merge_file_normalize_opts(&options, given_opts);
121
122 memset(&xmparam, 0x0, sizeof(xmparam_t));
123
124 if (ancestor) {
125 xmparam.ancestor = (options.ancestor_label) ?
126 options.ancestor_label : ancestor->path;
127 ancestor_mmfile.ptr = (char *)ancestor->ptr;
128 ancestor_mmfile.size = ancestor->size;
129 }
130
131 xmparam.file1 = (options.our_label) ?
132 options.our_label : ours->path;
133 our_mmfile.ptr = (char *)ours->ptr;
134 our_mmfile.size = ours->size;
135
136 xmparam.file2 = (options.their_label) ?
137 options.their_label : theirs->path;
138 their_mmfile.ptr = (char *)theirs->ptr;
139 their_mmfile.size = theirs->size;
140
141 if (options.favor == GIT_MERGE_FILE_FAVOR_OURS)
142 xmparam.favor = XDL_MERGE_FAVOR_OURS;
143 else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS)
144 xmparam.favor = XDL_MERGE_FAVOR_THEIRS;
145 else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION)
146 xmparam.favor = XDL_MERGE_FAVOR_UNION;
147
148 xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ?
149 XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS;
150
151 if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3)
152 xmparam.style = XDL_MERGE_DIFF3;
153
154 if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile,
155 &their_mmfile, &xmparam, &mmbuffer)) < 0) {
156 giterr_set(GITERR_MERGE, "Failed to merge files.");
157 error = -1;
158 goto done;
159 }
160
161 if ((path = merge_file_best_path(ancestor, ours, theirs)) != NULL &&
162 (out->path = strdup(path)) == NULL) {
163 error = -1;
164 goto done;
165 }
166
167 out->automergeable = (xdl_result == 0);
168 out->ptr = (unsigned char *)mmbuffer.ptr;
169 out->len = mmbuffer.size;
170 out->mode = merge_file_best_mode(ancestor, ours, theirs);
171
172 done:
173 if (error < 0)
174 git_merge_file_result_free(out);
175
176 return error;
177 }
178
179 static git_merge_file_input *git_merge_file__normalize_inputs(
180 git_merge_file_input *out,
181 const git_merge_file_input *given)
182 {
183 memcpy(out, given, sizeof(git_merge_file_input));
184
185 if (!out->path)
186 out->path = "file.txt";
187
188 if (!out->mode)
189 out->mode = 0100644;
190
191 return out;
192 }
193
194 int git_merge_file(
195 git_merge_file_result *out,
196 const git_merge_file_input *ancestor,
197 const git_merge_file_input *ours,
198 const git_merge_file_input *theirs,
199 const git_merge_file_options *options)
200 {
201 git_merge_file_input inputs[3] = { {0} };
202
203 assert(out && ours && theirs);
204
205 memset(out, 0x0, sizeof(git_merge_file_result));
206
207 if (ancestor)
208 ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor);
209
210 ours = git_merge_file__normalize_inputs(&inputs[1], ours);
211 theirs = git_merge_file__normalize_inputs(&inputs[2], theirs);
212
213 return git_merge_file__from_inputs(out, ancestor, ours, theirs, options);
214 }
215
216 int git_merge_file_from_index(
217 git_merge_file_result *out,
218 git_repository *repo,
219 const git_index_entry *ancestor,
220 const git_index_entry *ours,
221 const git_index_entry *theirs,
222 const git_merge_file_options *options)
223 {
224 git_merge_file_input inputs[3] = { {0} },
225 *ancestor_input = NULL, *our_input = NULL, *their_input = NULL;
226 git_odb *odb = NULL;
227 git_odb_object *odb_object[3] = { 0 };
228 int error = 0;
229
230 assert(out && repo && ours && theirs);
231
232 memset(out, 0x0, sizeof(git_merge_file_result));
233
234 if ((error = git_repository_odb(&odb, repo)) < 0)
235 goto done;
236
237 if (ancestor) {
238 if ((error = git_merge_file__input_from_index(
239 &inputs[0], &odb_object[0], odb, ancestor)) < 0)
240 goto done;
241
242 ancestor_input = &inputs[0];
243 }
244
245 if ((error = git_merge_file__input_from_index(
246 &inputs[1], &odb_object[1], odb, ours)) < 0)
247 goto done;
248
249 our_input = &inputs[1];
250
251 if ((error = git_merge_file__input_from_index(
252 &inputs[2], &odb_object[2], odb, theirs)) < 0)
253 goto done;
254
255 their_input = &inputs[2];
256
257 if ((error = git_merge_file__from_inputs(out,
258 ancestor_input, our_input, their_input, options)) < 0)
259 goto done;
260
261 done:
262 git_odb_object_free(odb_object[0]);
263 git_odb_object_free(odb_object[1]);
264 git_odb_object_free(odb_object[2]);
265 git_odb_free(odb);
266
267 return error;
268 }
269
270 void git_merge_file_result_free(git_merge_file_result *result)
271 {
272 if (result == NULL)
273 return;
274
275 git__free((char *)result->path);
276
277 /* xdiff uses malloc() not git_malloc, so we use free(), not git_free() */
278 free((char *)result->ptr);
279 }