]> git.proxmox.com Git - libgit2.git/blame - src/libgit2/merge_driver.c
Merge https://salsa.debian.org/debian/libgit2 into proxmox/bullseye
[libgit2.git] / src / libgit2 / merge_driver.c
CommitLineData
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
16static const char *merge_driver_name__text = "text";
17static const char *merge_driver_name__union = "union";
18static const char *merge_driver_name__binary = "binary";
19
20struct merge_driver_registry {
967e073d 21 git_rwlock lock;
3f04219f
ET
22 git_vector drivers;
23};
24
25typedef struct {
26 git_merge_driver *driver;
27 int initialized;
28 char name[GIT_FLEX_ARRAY];
29} git_merge_driver_entry;
30
967e073d 31static struct merge_driver_registry merge_driver_registry;
3f04219f 32
967e073d
ET
33static void git_merge_driver_global_shutdown(void);
34
c25aa7cd
PP
35git_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
42const 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
49const 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
56const 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
63const 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 70int 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
116done:
117 git_merge_file_result_free(&result);
118 return error;
119}
120
3f04219f
ET
121static 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
139static 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
147static 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
155git_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
165git_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
175git_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 */
183static 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
198int 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
219done:
220 if (error < 0)
967e073d 221 git_vector_free_deep(&merge_driver_registry.drivers);
3f04219f
ET
222
223 return error;
224}
225
967e073d 226static 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 */
248static 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 */
255static 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
266int 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
287done:
288 git_rwlock_wrunlock(&merge_driver_registry.lock);
289 return error;
3f04219f
ET
290}
291
292int 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
319done:
320 git_rwlock_wrunlock(&merge_driver_registry.lock);
321 return error;
3f04219f
ET
322}
323
324git_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
365static 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
400GIT_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 411int 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