File Coverage

deps/libgit2/src/merge_driver.c
Criterion Covered Total %
statement 74 149 49.6
branch 31 96 32.2
condition n/a
subroutine n/a
pod n/a
total 105 245 42.8


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