]>
Commit | Line | Data |
---|---|---|
3f04219f 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 | ||
eae0bfdc PP |
8 | #include "merge_driver.h" |
9 | ||
3f04219f ET |
10 | #include "vector.h" |
11 | #include "global.h" | |
12 | #include "merge.h" | |
13 | #include "git2/merge.h" | |
14 | #include "git2/sys/merge.h" | |
15 | ||
16 | static const char *merge_driver_name__text = "text"; | |
17 | static const char *merge_driver_name__union = "union"; | |
18 | static const char *merge_driver_name__binary = "binary"; | |
19 | ||
20 | struct merge_driver_registry { | |
967e073d | 21 | git_rwlock lock; |
3f04219f ET |
22 | git_vector drivers; |
23 | }; | |
24 | ||
25 | typedef struct { | |
26 | git_merge_driver *driver; | |
27 | int initialized; | |
28 | char name[GIT_FLEX_ARRAY]; | |
29 | } git_merge_driver_entry; | |
30 | ||
967e073d | 31 | static struct merge_driver_registry merge_driver_registry; |
3f04219f | 32 | |
967e073d ET |
33 | static void git_merge_driver_global_shutdown(void); |
34 | ||
0c9c969a | 35 | git_repository* git_merge_driver_source_repo(const git_merge_driver_source *src) |
0608d5df GA |
36 | { |
37 | assert(src); | |
38 | return src->repo; | |
39 | } | |
40 | ||
bb342159 | 41 | const git_index_entry* git_merge_driver_source_ancestor(const git_merge_driver_source *src) |
0608d5df GA |
42 | { |
43 | assert(src); | |
44 | return src->ancestor; | |
45 | } | |
46 | ||
bb342159 | 47 | const git_index_entry* git_merge_driver_source_ours(const git_merge_driver_source *src) |
0608d5df GA |
48 | { |
49 | assert(src); | |
50 | return src->ours; | |
51 | } | |
52 | ||
bb342159 | 53 | const git_index_entry* git_merge_driver_source_theirs(const git_merge_driver_source *src) |
0608d5df GA |
54 | { |
55 | assert(src); | |
56 | return src->theirs; | |
57 | } | |
58 | ||
bb342159 | 59 | const git_merge_file_options* git_merge_driver_source_file_options(const git_merge_driver_source *src) |
0608d5df GA |
60 | { |
61 | assert(src); | |
62 | return src->file_opts; | |
63 | } | |
967e073d | 64 | |
6d8b2cdb | 65 | int git_merge_driver__builtin_apply( |
3f04219f | 66 | git_merge_driver *self, |
3f04219f ET |
67 | const char **path_out, |
68 | uint32_t *mode_out, | |
69 | git_buf *merged_out, | |
6d8b2cdb | 70 | const char *filter_name, |
3f04219f ET |
71 | const git_merge_driver_source *src) |
72 | { | |
6d8b2cdb | 73 | git_merge_driver__builtin *driver = (git_merge_driver__builtin *)self; |
3f04219f ET |
74 | git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; |
75 | git_merge_file_result result = {0}; | |
76 | int error; | |
77 | ||
6d8b2cdb | 78 | GIT_UNUSED(filter_name); |
3f04219f ET |
79 | |
80 | if (src->file_opts) | |
81 | memcpy(&file_opts, src->file_opts, sizeof(git_merge_file_options)); | |
82 | ||
6d8b2cdb ET |
83 | if (driver->favor) |
84 | file_opts.favor = driver->favor; | |
3f04219f ET |
85 | |
86 | if ((error = git_merge_file_from_index(&result, src->repo, | |
87 | src->ancestor, src->ours, src->theirs, &file_opts)) < 0) | |
88 | goto done; | |
89 | ||
90 | if (!result.automergeable && | |
91 | !(file_opts.flags & GIT_MERGE_FILE_FAVOR__CONFLICTED)) { | |
92 | error = GIT_EMERGECONFLICT; | |
93 | goto done; | |
94 | } | |
95 | ||
96 | *path_out = git_merge_file__best_path( | |
97 | src->ancestor ? src->ancestor->path : NULL, | |
98 | src->ours ? src->ours->path : NULL, | |
99 | src->theirs ? src->theirs->path : NULL); | |
100 | ||
101 | *mode_out = git_merge_file__best_mode( | |
102 | src->ancestor ? src->ancestor->mode : 0, | |
103 | src->ours ? src->ours->mode : 0, | |
104 | src->theirs ? src->theirs->mode : 0); | |
105 | ||
106 | merged_out->ptr = (char *)result.ptr; | |
107 | merged_out->size = result.len; | |
108 | merged_out->asize = result.len; | |
109 | result.ptr = NULL; | |
110 | ||
111 | done: | |
112 | git_merge_file_result_free(&result); | |
113 | return error; | |
114 | } | |
115 | ||
3f04219f ET |
116 | static int merge_driver_binary_apply( |
117 | git_merge_driver *self, | |
3f04219f ET |
118 | const char **path_out, |
119 | uint32_t *mode_out, | |
120 | git_buf *merged_out, | |
6d8b2cdb | 121 | const char *filter_name, |
3f04219f ET |
122 | const git_merge_driver_source *src) |
123 | { | |
124 | GIT_UNUSED(self); | |
3f04219f ET |
125 | GIT_UNUSED(path_out); |
126 | GIT_UNUSED(mode_out); | |
127 | GIT_UNUSED(merged_out); | |
6d8b2cdb | 128 | GIT_UNUSED(filter_name); |
3f04219f ET |
129 | GIT_UNUSED(src); |
130 | ||
131 | return GIT_EMERGECONFLICT; | |
132 | } | |
133 | ||
134 | static int merge_driver_entry_cmp(const void *a, const void *b) | |
135 | { | |
136 | const git_merge_driver_entry *entry_a = a; | |
137 | const git_merge_driver_entry *entry_b = b; | |
138 | ||
139 | return strcmp(entry_a->name, entry_b->name); | |
140 | } | |
141 | ||
142 | static int merge_driver_entry_search(const void *a, const void *b) | |
143 | { | |
144 | const char *name_a = a; | |
145 | const git_merge_driver_entry *entry_b = b; | |
146 | ||
147 | return strcmp(name_a, entry_b->name); | |
148 | } | |
149 | ||
6d8b2cdb ET |
150 | git_merge_driver__builtin git_merge_driver__text = { |
151 | { | |
152 | GIT_MERGE_DRIVER_VERSION, | |
153 | NULL, | |
154 | NULL, | |
155 | git_merge_driver__builtin_apply, | |
156 | }, | |
157 | GIT_MERGE_FILE_FAVOR_NORMAL | |
3f04219f ET |
158 | }; |
159 | ||
6d8b2cdb ET |
160 | git_merge_driver__builtin git_merge_driver__union = { |
161 | { | |
162 | GIT_MERGE_DRIVER_VERSION, | |
163 | NULL, | |
164 | NULL, | |
165 | git_merge_driver__builtin_apply, | |
166 | }, | |
167 | GIT_MERGE_FILE_FAVOR_UNION | |
3f04219f ET |
168 | }; |
169 | ||
170 | git_merge_driver git_merge_driver__binary = { | |
171 | GIT_MERGE_DRIVER_VERSION, | |
172 | NULL, | |
173 | NULL, | |
3f04219f ET |
174 | merge_driver_binary_apply |
175 | }; | |
176 | ||
967e073d ET |
177 | /* Note: callers must lock the registry before calling this function */ |
178 | static int merge_driver_registry_insert( | |
179 | const char *name, git_merge_driver *driver) | |
3f04219f | 180 | { |
967e073d | 181 | git_merge_driver_entry *entry; |
3f04219f | 182 | |
967e073d | 183 | entry = git__calloc(1, sizeof(git_merge_driver_entry) + strlen(name) + 1); |
ac3d33df | 184 | GIT_ERROR_CHECK_ALLOC(entry); |
3f04219f | 185 | |
967e073d ET |
186 | strcpy(entry->name, name); |
187 | entry->driver = driver; | |
3f04219f | 188 | |
967e073d ET |
189 | return git_vector_insert_sorted( |
190 | &merge_driver_registry.drivers, entry, NULL); | |
191 | } | |
3f04219f | 192 | |
967e073d ET |
193 | int git_merge_driver_global_init(void) |
194 | { | |
195 | int error; | |
3f04219f | 196 | |
967e073d ET |
197 | if (git_rwlock_init(&merge_driver_registry.lock) < 0) |
198 | return -1; | |
3f04219f | 199 | |
967e073d ET |
200 | if ((error = git_vector_init(&merge_driver_registry.drivers, 3, |
201 | merge_driver_entry_cmp)) < 0) | |
202 | goto done; | |
203 | ||
204 | if ((error = merge_driver_registry_insert( | |
6d8b2cdb | 205 | merge_driver_name__text, &git_merge_driver__text.base)) < 0 || |
967e073d | 206 | (error = merge_driver_registry_insert( |
6d8b2cdb | 207 | merge_driver_name__union, &git_merge_driver__union.base)) < 0 || |
967e073d | 208 | (error = merge_driver_registry_insert( |
3f04219f | 209 | merge_driver_name__binary, &git_merge_driver__binary)) < 0) |
83c93a7c | 210 | goto done; |
967e073d ET |
211 | |
212 | git__on_shutdown(git_merge_driver_global_shutdown); | |
3f04219f ET |
213 | |
214 | done: | |
215 | if (error < 0) | |
967e073d | 216 | git_vector_free_deep(&merge_driver_registry.drivers); |
3f04219f ET |
217 | |
218 | return error; | |
219 | } | |
220 | ||
967e073d | 221 | static void git_merge_driver_global_shutdown(void) |
3f04219f ET |
222 | { |
223 | git_merge_driver_entry *entry; | |
967e073d ET |
224 | size_t i; |
225 | ||
226 | if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) | |
227 | return; | |
228 | ||
229 | git_vector_foreach(&merge_driver_registry.drivers, i, entry) { | |
230 | if (entry->driver->shutdown) | |
231 | entry->driver->shutdown(entry->driver); | |
232 | ||
233 | git__free(entry); | |
234 | } | |
235 | ||
236 | git_vector_free(&merge_driver_registry.drivers); | |
237 | ||
238 | git_rwlock_wrunlock(&merge_driver_registry.lock); | |
239 | git_rwlock_free(&merge_driver_registry.lock); | |
240 | } | |
241 | ||
242 | /* Note: callers must lock the registry before calling this function */ | |
243 | static int merge_driver_registry_find(size_t *pos, const char *name) | |
244 | { | |
245 | return git_vector_search2(pos, &merge_driver_registry.drivers, | |
246 | merge_driver_entry_search, name); | |
247 | } | |
248 | ||
249 | /* Note: callers must lock the registry before calling this function */ | |
250 | static git_merge_driver_entry *merge_driver_registry_lookup( | |
251 | size_t *pos, const char *name) | |
252 | { | |
253 | git_merge_driver_entry *entry = NULL; | |
254 | ||
255 | if (!merge_driver_registry_find(pos, name)) | |
256 | entry = git_vector_get(&merge_driver_registry.drivers, *pos); | |
257 | ||
258 | return entry; | |
259 | } | |
260 | ||
261 | int git_merge_driver_register(const char *name, git_merge_driver *driver) | |
262 | { | |
263 | int error; | |
3f04219f ET |
264 | |
265 | assert(name && driver); | |
266 | ||
967e073d | 267 | if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { |
ac3d33df | 268 | git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); |
3f04219f | 269 | return -1; |
967e073d | 270 | } |
3f04219f | 271 | |
967e073d | 272 | if (!merge_driver_registry_find(NULL, name)) { |
ac3d33df | 273 | git_error_set(GIT_ERROR_MERGE, "attempt to reregister existing driver '%s'", |
967e073d ET |
274 | name); |
275 | error = GIT_EEXISTS; | |
276 | goto done; | |
277 | } | |
3f04219f | 278 | |
967e073d | 279 | error = merge_driver_registry_insert(name, driver); |
3f04219f | 280 | |
967e073d ET |
281 | done: |
282 | git_rwlock_wrunlock(&merge_driver_registry.lock); | |
283 | return error; | |
3f04219f ET |
284 | } |
285 | ||
286 | int git_merge_driver_unregister(const char *name) | |
287 | { | |
288 | git_merge_driver_entry *entry; | |
289 | size_t pos; | |
967e073d ET |
290 | int error = 0; |
291 | ||
292 | if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { | |
ac3d33df | 293 | git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); |
967e073d ET |
294 | return -1; |
295 | } | |
3f04219f | 296 | |
967e073d | 297 | if ((entry = merge_driver_registry_lookup(&pos, name)) == NULL) { |
ac3d33df | 298 | git_error_set(GIT_ERROR_MERGE, "cannot find merge driver '%s' to unregister", |
967e073d ET |
299 | name); |
300 | error = GIT_ENOTFOUND; | |
301 | goto done; | |
302 | } | |
3f04219f | 303 | |
967e073d | 304 | git_vector_remove(&merge_driver_registry.drivers, pos); |
3f04219f ET |
305 | |
306 | if (entry->initialized && entry->driver->shutdown) { | |
307 | entry->driver->shutdown(entry->driver); | |
308 | entry->initialized = false; | |
309 | } | |
310 | ||
311 | git__free(entry); | |
312 | ||
967e073d ET |
313 | done: |
314 | git_rwlock_wrunlock(&merge_driver_registry.lock); | |
315 | return error; | |
3f04219f ET |
316 | } |
317 | ||
318 | git_merge_driver *git_merge_driver_lookup(const char *name) | |
319 | { | |
320 | git_merge_driver_entry *entry; | |
321 | size_t pos; | |
322 | int error; | |
323 | ||
324 | /* If we've decided the merge driver to use internally - and not | |
325 | * based on user configuration (in merge_driver_name_for_path) | |
967e073d ET |
326 | * then we can use a hardcoded name to compare instead of bothering |
327 | * to take a lock and look it up in the vector. | |
3f04219f ET |
328 | */ |
329 | if (name == merge_driver_name__text) | |
6d8b2cdb | 330 | return &git_merge_driver__text.base; |
3f04219f ET |
331 | else if (name == merge_driver_name__binary) |
332 | return &git_merge_driver__binary; | |
333 | ||
967e073d | 334 | if (git_rwlock_rdlock(&merge_driver_registry.lock) < 0) { |
ac3d33df | 335 | git_error_set(GIT_ERROR_OS, "failed to lock merge driver registry"); |
3f04219f | 336 | return NULL; |
967e073d | 337 | } |
3f04219f | 338 | |
967e073d | 339 | entry = merge_driver_registry_lookup(&pos, name); |
3f04219f | 340 | |
967e073d | 341 | git_rwlock_rdunlock(&merge_driver_registry.lock); |
3f04219f | 342 | |
967e073d | 343 | if (entry == NULL) { |
ac3d33df | 344 | git_error_set(GIT_ERROR_MERGE, "cannot use an unregistered filter"); |
967e073d ET |
345 | return NULL; |
346 | } | |
3f04219f ET |
347 | |
348 | if (!entry->initialized) { | |
349 | if (entry->driver->initialize && | |
350 | (error = entry->driver->initialize(entry->driver)) < 0) | |
351 | return NULL; | |
352 | ||
353 | entry->initialized = 1; | |
354 | } | |
355 | ||
46625836 | 356 | return entry->driver; |
3f04219f ET |
357 | } |
358 | ||
3f04219f ET |
359 | static int merge_driver_name_for_path( |
360 | const char **out, | |
361 | git_repository *repo, | |
30a94ab7 ET |
362 | const char *path, |
363 | const char *default_driver) | |
3f04219f ET |
364 | { |
365 | const char *value; | |
366 | int error; | |
367 | ||
368 | *out = NULL; | |
369 | ||
370 | if ((error = git_attr_get(&value, repo, 0, path, "merge")) < 0) | |
371 | return error; | |
372 | ||
373 | /* set: use the built-in 3-way merge driver ("text") */ | |
0c9c969a | 374 | if (GIT_ATTR_IS_TRUE(value)) |
3f04219f | 375 | *out = merge_driver_name__text; |
3f04219f ET |
376 | |
377 | /* unset: do not merge ("binary") */ | |
0c9c969a | 378 | else if (GIT_ATTR_IS_FALSE(value)) |
3f04219f | 379 | *out = merge_driver_name__binary; |
3f04219f | 380 | |
0c9c969a | 381 | else if (GIT_ATTR_IS_UNSPECIFIED(value) && default_driver) |
30a94ab7 ET |
382 | *out = default_driver; |
383 | ||
0c9c969a | 384 | else if (GIT_ATTR_IS_UNSPECIFIED(value)) |
3f04219f | 385 | *out = merge_driver_name__text; |
30a94ab7 ET |
386 | |
387 | else | |
388 | *out = value; | |
389 | ||
3f04219f ET |
390 | return 0; |
391 | } | |
392 | ||
30a94ab7 ET |
393 | |
394 | GIT_INLINE(git_merge_driver *) merge_driver_lookup_with_wildcard( | |
395 | const char *name) | |
396 | { | |
397 | git_merge_driver *driver = git_merge_driver_lookup(name); | |
398 | ||
399 | if (driver == NULL) | |
400 | driver = git_merge_driver_lookup("*"); | |
401 | ||
402 | return driver; | |
403 | } | |
404 | ||
3f04219f | 405 | int git_merge_driver_for_source( |
6d8b2cdb | 406 | const char **name_out, |
3f04219f | 407 | git_merge_driver **driver_out, |
3f04219f ET |
408 | const git_merge_driver_source *src) |
409 | { | |
410 | const char *path, *driver_name; | |
3f04219f ET |
411 | int error = 0; |
412 | ||
413 | path = git_merge_file__best_path( | |
414 | src->ancestor ? src->ancestor->path : NULL, | |
415 | src->ours ? src->ours->path : NULL, | |
416 | src->theirs ? src->theirs->path : NULL); | |
417 | ||
30a94ab7 ET |
418 | if ((error = merge_driver_name_for_path( |
419 | &driver_name, src->repo, path, src->default_driver)) < 0) | |
3f04219f ET |
420 | return error; |
421 | ||
6d8b2cdb ET |
422 | *name_out = driver_name; |
423 | *driver_out = merge_driver_lookup_with_wildcard(driver_name); | |
3f04219f ET |
424 | return error; |
425 | } | |
426 |