]>
Commit | Line | Data |
---|---|---|
bec65a5e ET |
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" | |
05d47768 ET |
11 | #include "posix.h" |
12 | #include "fileops.h" | |
13 | #include "index.h" | |
bec65a5e ET |
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 | { | |
05d47768 ET |
28 | if (!ancestor) { |
29 | if (ours && theirs && strcmp(ours->path, theirs->path) == 0) | |
bec65a5e ET |
30 | return ours->path; |
31 | ||
32 | return NULL; | |
33 | } | |
1fed6b07 | 34 | |
05d47768 ET |
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; | |
1fed6b07 | 39 | |
bec65a5e ET |
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 | */ | |
05d47768 ET |
53 | if (!ancestor) { |
54 | if ((ours && ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE) || | |
55 | (theirs && theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE)) | |
bec65a5e | 56 | return GIT_FILEMODE_BLOB_EXECUTABLE; |
1fed6b07 | 57 | |
bec65a5e | 58 | return GIT_FILEMODE_BLOB; |
05d47768 ET |
59 | } else if (ours && theirs) { |
60 | if (ancestor->mode == ours->mode) | |
61 | return theirs->mode; | |
1fed6b07 | 62 | |
bec65a5e | 63 | return ours->mode; |
05d47768 | 64 | } |
1fed6b07 | 65 | |
bec65a5e ET |
66 | return 0; |
67 | } | |
68 | ||
05d47768 ET |
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, | |
bec65a5e ET |
73 | const git_index_entry *entry) |
74 | { | |
bec65a5e | 75 | int error = 0; |
1fed6b07 | 76 | |
05d47768 | 77 | assert(input_out && odb_object_out && odb && entry); |
1fed6b07 | 78 | |
05d47768 | 79 | if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0) |
bec65a5e | 80 | goto done; |
1fed6b07 | 81 | |
05d47768 ET |
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); | |
1fed6b07 | 86 | |
bec65a5e | 87 | done: |
bec65a5e ET |
88 | return error; |
89 | } | |
90 | ||
05d47768 ET |
91 | static void merge_file_normalize_opts( |
92 | git_merge_file_options *out, | |
93 | const git_merge_file_options *given_opts) | |
bec65a5e | 94 | { |
05d47768 ET |
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 | } | |
bec65a5e ET |
101 | } |
102 | ||
05d47768 | 103 | static int git_merge_file__from_inputs( |
bec65a5e | 104 | git_merge_file_result *out, |
05d47768 ET |
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) | |
bec65a5e ET |
109 | { |
110 | xmparam_t xmparam; | |
05d47768 | 111 | mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0}; |
bec65a5e | 112 | mmbuffer_t mmbuffer; |
05d47768 ET |
113 | git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT; |
114 | const char *path; | |
bec65a5e ET |
115 | int xdl_result; |
116 | int error = 0; | |
117 | ||
bec65a5e ET |
118 | memset(out, 0x0, sizeof(git_merge_file_result)); |
119 | ||
7dcd42a5 | 120 | merge_file_normalize_opts(&options, given_opts); |
1fed6b07 | 121 | |
bec65a5e | 122 | memset(&xmparam, 0x0, sizeof(xmparam_t)); |
bec65a5e | 123 | |
05d47768 ET |
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 | } | |
bec65a5e | 130 | |
05d47768 ET |
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) | |
bec65a5e | 142 | xmparam.favor = XDL_MERGE_FAVOR_OURS; |
05d47768 | 143 | else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS) |
bec65a5e | 144 | xmparam.favor = XDL_MERGE_FAVOR_THEIRS; |
05d47768 | 145 | else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION) |
db3462ce | 146 | xmparam.favor = XDL_MERGE_FAVOR_UNION; |
bec65a5e | 147 | |
05d47768 | 148 | xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ? |
c1d648c5 ET |
149 | XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS; |
150 | ||
05d47768 | 151 | if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3) |
e651e8e2 ET |
152 | xmparam.style = XDL_MERGE_DIFF3; |
153 | ||
13de9363 | 154 | if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE) |
45a86bbf | 155 | xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE; |
13de9363 | 156 | if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE) |
45a86bbf | 157 | xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_CHANGE; |
13de9363 | 158 | if (options.flags & GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL) |
45a86bbf JG |
159 | xmparam.xpp.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; |
160 | ||
74c37c2a JG |
161 | if (options.flags & GIT_MERGE_FILE_DIFF_PATIENCE) |
162 | xmparam.xpp.flags |= XDF_PATIENCE_DIFF; | |
163 | ||
164 | if (options.flags & GIT_MERGE_FILE_DIFF_MINIMAL) | |
165 | xmparam.xpp.flags |= XDF_NEED_MINIMAL; | |
166 | ||
05d47768 ET |
167 | if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile, |
168 | &their_mmfile, &xmparam, &mmbuffer)) < 0) { | |
bec65a5e ET |
169 | giterr_set(GITERR_MERGE, "Failed to merge files."); |
170 | error = -1; | |
171 | goto done; | |
172 | } | |
1fed6b07 | 173 | |
05d47768 ET |
174 | if ((path = merge_file_best_path(ancestor, ours, theirs)) != NULL && |
175 | (out->path = strdup(path)) == NULL) { | |
176 | error = -1; | |
177 | goto done; | |
178 | } | |
179 | ||
bec65a5e | 180 | out->automergeable = (xdl_result == 0); |
7dcd42a5 | 181 | out->ptr = (const char *)mmbuffer.ptr; |
bec65a5e | 182 | out->len = mmbuffer.size; |
05d47768 | 183 | out->mode = merge_file_best_mode(ancestor, ours, theirs); |
bec65a5e ET |
184 | |
185 | done: | |
05d47768 ET |
186 | if (error < 0) |
187 | git_merge_file_result_free(out); | |
188 | ||
bec65a5e ET |
189 | return error; |
190 | } | |
05d47768 ET |
191 | |
192 | static git_merge_file_input *git_merge_file__normalize_inputs( | |
193 | git_merge_file_input *out, | |
194 | const git_merge_file_input *given) | |
195 | { | |
196 | memcpy(out, given, sizeof(git_merge_file_input)); | |
197 | ||
198 | if (!out->path) | |
199 | out->path = "file.txt"; | |
200 | ||
201 | if (!out->mode) | |
202 | out->mode = 0100644; | |
203 | ||
204 | return out; | |
205 | } | |
206 | ||
207 | int git_merge_file( | |
208 | git_merge_file_result *out, | |
209 | const git_merge_file_input *ancestor, | |
210 | const git_merge_file_input *ours, | |
211 | const git_merge_file_input *theirs, | |
212 | const git_merge_file_options *options) | |
213 | { | |
214 | git_merge_file_input inputs[3] = { {0} }; | |
215 | ||
216 | assert(out && ours && theirs); | |
217 | ||
218 | memset(out, 0x0, sizeof(git_merge_file_result)); | |
219 | ||
220 | if (ancestor) | |
221 | ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor); | |
222 | ||
223 | ours = git_merge_file__normalize_inputs(&inputs[1], ours); | |
224 | theirs = git_merge_file__normalize_inputs(&inputs[2], theirs); | |
225 | ||
226 | return git_merge_file__from_inputs(out, ancestor, ours, theirs, options); | |
227 | } | |
228 | ||
229 | int git_merge_file_from_index( | |
230 | git_merge_file_result *out, | |
231 | git_repository *repo, | |
232 | const git_index_entry *ancestor, | |
233 | const git_index_entry *ours, | |
234 | const git_index_entry *theirs, | |
235 | const git_merge_file_options *options) | |
236 | { | |
237 | git_merge_file_input inputs[3] = { {0} }, | |
238 | *ancestor_input = NULL, *our_input = NULL, *their_input = NULL; | |
239 | git_odb *odb = NULL; | |
240 | git_odb_object *odb_object[3] = { 0 }; | |
241 | int error = 0; | |
242 | ||
243 | assert(out && repo && ours && theirs); | |
244 | ||
245 | memset(out, 0x0, sizeof(git_merge_file_result)); | |
246 | ||
247 | if ((error = git_repository_odb(&odb, repo)) < 0) | |
248 | goto done; | |
249 | ||
250 | if (ancestor) { | |
251 | if ((error = git_merge_file__input_from_index( | |
252 | &inputs[0], &odb_object[0], odb, ancestor)) < 0) | |
253 | goto done; | |
254 | ||
255 | ancestor_input = &inputs[0]; | |
256 | } | |
257 | ||
258 | if ((error = git_merge_file__input_from_index( | |
259 | &inputs[1], &odb_object[1], odb, ours)) < 0) | |
260 | goto done; | |
261 | ||
262 | our_input = &inputs[1]; | |
263 | ||
264 | if ((error = git_merge_file__input_from_index( | |
265 | &inputs[2], &odb_object[2], odb, theirs)) < 0) | |
266 | goto done; | |
267 | ||
268 | their_input = &inputs[2]; | |
269 | ||
270 | if ((error = git_merge_file__from_inputs(out, | |
271 | ancestor_input, our_input, their_input, options)) < 0) | |
272 | goto done; | |
273 | ||
274 | done: | |
275 | git_odb_object_free(odb_object[0]); | |
276 | git_odb_object_free(odb_object[1]); | |
277 | git_odb_object_free(odb_object[2]); | |
278 | git_odb_free(odb); | |
279 | ||
280 | return error; | |
281 | } | |
282 | ||
283 | void git_merge_file_result_free(git_merge_file_result *result) | |
284 | { | |
285 | if (result == NULL) | |
286 | return; | |
287 | ||
7f930ded | 288 | git__free((char *)result->path); |
05d47768 ET |
289 | |
290 | /* xdiff uses malloc() not git_malloc, so we use free(), not git_free() */ | |
7f930ded | 291 | free((char *)result->ptr); |
05d47768 | 292 | } |