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