File Coverage

deps/libgit2/src/libgit2/diff_driver.c
Criterion Covered Total %
statement 39 243 16.0
branch 15 178 8.4
condition n/a
subroutine n/a
pod n/a
total 54 421 12.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 "diff_driver.h"
9              
10             #include "git2/attr.h"
11              
12             #include "common.h"
13             #include "diff.h"
14             #include "strmap.h"
15             #include "map.h"
16             #include "config.h"
17             #include "regexp.h"
18             #include "repository.h"
19              
20             typedef enum {
21             DIFF_DRIVER_AUTO = 0,
22             DIFF_DRIVER_BINARY = 1,
23             DIFF_DRIVER_TEXT = 2,
24             DIFF_DRIVER_PATTERNLIST = 3
25             } git_diff_driver_t;
26              
27             typedef struct {
28             git_regexp re;
29             int flags;
30             } git_diff_driver_pattern;
31              
32             enum {
33             REG_NEGATE = (1 << 15) /* get out of the way of existing flags */
34             };
35              
36             /* data for finding function context for a given file type */
37             struct git_diff_driver {
38             git_diff_driver_t type;
39             uint32_t binary_flags;
40             uint32_t other_flags;
41             git_array_t(git_diff_driver_pattern) fn_patterns;
42             git_regexp word_pattern;
43             char name[GIT_FLEX_ARRAY];
44             };
45              
46             #include "userdiff.h"
47              
48             struct git_diff_driver_registry {
49             git_strmap *drivers;
50             };
51              
52             #define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY)
53              
54             static git_diff_driver diff_driver_auto = { DIFF_DRIVER_AUTO, 0, 0 };
55             static git_diff_driver diff_driver_binary = { DIFF_DRIVER_BINARY, GIT_DIFF_FORCE_BINARY, 0 };
56             static git_diff_driver diff_driver_text = { DIFF_DRIVER_TEXT, GIT_DIFF_FORCE_TEXT, 0 };
57              
58 0           git_diff_driver_registry *git_diff_driver_registry_new(void)
59             {
60 0           git_diff_driver_registry *reg =
61 0           git__calloc(1, sizeof(git_diff_driver_registry));
62 0 0         if (!reg)
63 0           return NULL;
64              
65 0 0         if (git_strmap_new(®->drivers) < 0) {
66 0           git_diff_driver_registry_free(reg);
67 0           return NULL;
68             }
69              
70 0           return reg;
71             }
72              
73 63           void git_diff_driver_registry_free(git_diff_driver_registry *reg)
74             {
75             git_diff_driver *drv;
76              
77 63 50         if (!reg)
78 63           return;
79              
80 0 0         git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv));
81 0           git_strmap_free(reg->drivers);
82 0           git__free(reg);
83             }
84              
85 0           static int diff_driver_add_patterns(
86             git_diff_driver *drv, const char *regex_str, int regex_flags)
87             {
88 0           int error = 0;
89             const char *scan, *end;
90 0           git_diff_driver_pattern *pat = NULL;
91 0           git_str buf = GIT_STR_INIT;
92              
93 0 0         for (scan = regex_str; scan; scan = end) {
94             /* get pattern to fill in */
95 0 0         if ((pat = git_array_alloc(drv->fn_patterns)) == NULL) {
    0          
    0          
96 0           return -1;
97             }
98              
99 0           pat->flags = regex_flags;
100 0 0         if (*scan == '!') {
101 0           pat->flags |= REG_NEGATE;
102 0           ++scan;
103             }
104              
105 0 0         if ((end = strchr(scan, '\n')) != NULL) {
106 0           error = git_str_set(&buf, scan, end - scan);
107 0           end++;
108             } else {
109 0           error = git_str_sets(&buf, scan);
110             }
111 0 0         if (error < 0)
112 0           break;
113              
114 0           if ((error = git_regexp_compile(&pat->re, buf.ptr, regex_flags)) != 0) {
115             /*
116             * TODO: issue a warning
117             */
118             }
119             }
120              
121 0 0         if (error && pat != NULL)
    0          
122 0 0         (void)git_array_pop(drv->fn_patterns); /* release last item */
123 0           git_str_dispose(&buf);
124              
125             /* We want to ignore bad patterns, so return success regardless */
126 0           return 0;
127             }
128              
129 0           static int diff_driver_xfuncname(const git_config_entry *entry, void *payload)
130             {
131 0           return diff_driver_add_patterns(payload, entry->value, 0);
132             }
133              
134 0           static int diff_driver_funcname(const git_config_entry *entry, void *payload)
135             {
136 0           return diff_driver_add_patterns(payload, entry->value, 0);
137             }
138              
139 0           static git_diff_driver_registry *git_repository_driver_registry(
140             git_repository *repo)
141             {
142 0           git_diff_driver_registry *reg = git_atomic_load(repo->diff_drivers), *newreg;
143 0 0         if (reg)
144 0           return reg;
145              
146 0           newreg = git_diff_driver_registry_new();
147 0 0         if (!newreg) {
148 0           git_error_set(GIT_ERROR_REPOSITORY, "unable to create diff driver registry");
149 0           return newreg;
150             }
151 0           reg = git_atomic_compare_and_swap(&repo->diff_drivers, NULL, newreg);
152 0 0         if (!reg) {
153 0           reg = newreg;
154             } else {
155             /* if we race, free losing allocation */
156 0           git_diff_driver_registry_free(newreg);
157             }
158 0           return reg;
159             }
160              
161 0           static int diff_driver_alloc(
162             git_diff_driver **out, size_t *namelen_out, const char *name)
163             {
164             git_diff_driver *driver;
165 0           size_t driverlen = sizeof(git_diff_driver),
166 0           namelen = strlen(name),
167             alloclen;
168              
169 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, driverlen, namelen);
    0          
170 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
    0          
171              
172 0           driver = git__calloc(1, alloclen);
173 0 0         GIT_ERROR_CHECK_ALLOC(driver);
174              
175 0           memcpy(driver->name, name, namelen);
176              
177 0           *out = driver;
178              
179 0 0         if (namelen_out)
180 0           *namelen_out = namelen;
181              
182 0           return 0;
183             }
184              
185 0           static int git_diff_driver_builtin(
186             git_diff_driver **out,
187             git_diff_driver_registry *reg,
188             const char *driver_name)
189             {
190 0           git_diff_driver_definition *ddef = NULL;
191 0           git_diff_driver *drv = NULL;
192 0           int error = 0;
193             size_t idx;
194              
195 0 0         for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) {
196 0 0         if (!strcasecmp(driver_name, builtin_defs[idx].name)) {
197 0           ddef = &builtin_defs[idx];
198 0           break;
199             }
200             }
201 0 0         if (!ddef)
202 0           goto done;
203              
204 0 0         if ((error = diff_driver_alloc(&drv, NULL, ddef->name)) < 0)
205 0           goto done;
206              
207 0           drv->type = DIFF_DRIVER_PATTERNLIST;
208              
209 0 0         if (ddef->fns &&
    0          
210 0           (error = diff_driver_add_patterns(
211             drv, ddef->fns, ddef->flags)) < 0)
212 0           goto done;
213              
214 0 0         if (ddef->words &&
    0          
215 0           (error = git_regexp_compile(&drv->word_pattern, ddef->words, ddef->flags)) < 0)
216 0           goto done;
217              
218 0 0         if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0)
219 0           goto done;
220              
221             done:
222 0 0         if (error && drv)
    0          
223 0           git_diff_driver_free(drv);
224             else
225 0           *out = drv;
226              
227 0           return error;
228             }
229              
230 0           static int git_diff_driver_load(
231             git_diff_driver **out, git_repository *repo, const char *driver_name)
232             {
233 0           int error = 0;
234             git_diff_driver_registry *reg;
235             git_diff_driver *drv;
236             size_t namelen;
237 0           git_config *cfg = NULL;
238 0           git_str name = GIT_STR_INIT;
239 0           git_config_entry *ce = NULL;
240 0           bool found_driver = false;
241              
242 0 0         if ((reg = git_repository_driver_registry(repo)) == NULL)
243 0           return -1;
244              
245 0 0         if ((drv = git_strmap_get(reg->drivers, driver_name)) != NULL) {
246 0           *out = drv;
247 0           return 0;
248             }
249              
250 0 0         if ((error = diff_driver_alloc(&drv, &namelen, driver_name)) < 0)
251 0           goto done;
252              
253 0           drv->type = DIFF_DRIVER_AUTO;
254              
255             /* if you can't read config for repo, just use default driver */
256 0 0         if (git_repository_config_snapshot(&cfg, repo) < 0) {
257 0           git_error_clear();
258 0           goto done;
259             }
260              
261 0 0         if ((error = git_str_printf(&name, "diff.%s.binary", driver_name)) < 0)
262 0           goto done;
263              
264 0           switch (git_config__get_bool_force(cfg, name.ptr, -1)) {
265             case true:
266             /* if diff..binary is true, just return the binary driver */
267 0           *out = &diff_driver_binary;
268 0           goto done;
269             case false:
270             /* if diff..binary is false, force binary checks off */
271             /* but still may have custom function context patterns, etc. */
272 0           drv->binary_flags = GIT_DIFF_FORCE_TEXT;
273 0           found_driver = true;
274 0           break;
275             default:
276             /* diff..binary unspecified or "auto", so just continue */
277 0           break;
278             }
279              
280             /* TODO: warn if diff..command or diff..textconv are set */
281              
282 0           git_str_truncate(&name, namelen + strlen("diff.."));
283 0 0         if ((error = git_str_PUTS(&name, "xfuncname")) < 0)
284 0           goto done;
285              
286 0 0         if ((error = git_config_get_multivar_foreach(
287 0           cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) {
288 0 0         if (error != GIT_ENOTFOUND)
289 0           goto done;
290 0           git_error_clear(); /* no diff..xfuncname, so just continue */
291             }
292              
293 0           git_str_truncate(&name, namelen + strlen("diff.."));
294 0 0         if ((error = git_str_PUTS(&name, "funcname")) < 0)
295 0           goto done;
296              
297 0 0         if ((error = git_config_get_multivar_foreach(
298 0           cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) {
299 0 0         if (error != GIT_ENOTFOUND)
300 0           goto done;
301 0           git_error_clear(); /* no diff..funcname, so just continue */
302             }
303              
304             /* if we found any patterns, set driver type to use correct callback */
305 0 0         if (git_array_size(drv->fn_patterns) > 0) {
306 0           drv->type = DIFF_DRIVER_PATTERNLIST;
307 0           found_driver = true;
308             }
309              
310 0           git_str_truncate(&name, namelen + strlen("diff.."));
311 0 0         if ((error = git_str_PUTS(&name, "wordregex")) < 0)
312 0           goto done;
313              
314 0 0         if ((error = git_config__lookup_entry(&ce, cfg, name.ptr, false)) < 0)
315 0           goto done;
316 0 0         if (!ce || !ce->value)
    0          
317             /* no diff..wordregex, so just continue */;
318 0 0         else if (!(error = git_regexp_compile(&drv->word_pattern, ce->value, 0)))
319 0           found_driver = true;
320             else {
321             /* TODO: warn about bad regex instead of failure */
322 0           goto done;
323             }
324              
325             /* TODO: look up diff..algorithm to turn on minimal / patience
326             * diff in drv->other_flags
327             */
328              
329             /* if no driver config found at all, fall back on AUTO driver */
330 0 0         if (!found_driver)
331 0           goto done;
332              
333             /* store driver in registry */
334 0 0         if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0)
335 0           goto done;
336              
337 0           *out = drv;
338              
339             done:
340 0           git_config_entry_free(ce);
341 0           git_str_dispose(&name);
342 0           git_config_free(cfg);
343              
344 0 0         if (!*out) {
345 0           int error2 = git_diff_driver_builtin(out, reg, driver_name);
346 0 0         if (!error)
347 0           error = error2;
348             }
349              
350 0 0         if (drv && drv != *out)
    0          
351 0           git_diff_driver_free(drv);
352              
353 0           return error;
354             }
355              
356 100           int git_diff_driver_lookup(
357             git_diff_driver **out, git_repository *repo,
358             git_attr_session *attrsession, const char *path)
359             {
360 100           int error = 0;
361 100           const char *values[1], *attrs[] = { "diff" };
362              
363 100 50         GIT_ASSERT_ARG(out);
364 100           *out = NULL;
365              
366 100 50         if (!repo || !path || !strlen(path))
    50          
    50          
367             /* just use the auto value */;
368 100 50         else if ((error = git_attr_get_many_with_session(values, repo,
369             attrsession, 0, path, 1, attrs)) < 0)
370             /* return error below */;
371              
372 100 50         else if (GIT_ATTR_IS_UNSPECIFIED(values[0]))
373             /* just use the auto value */;
374 0 0         else if (GIT_ATTR_IS_FALSE(values[0]))
375 0           *out = &diff_driver_binary;
376 0 0         else if (GIT_ATTR_IS_TRUE(values[0]))
377 0           *out = &diff_driver_text;
378              
379             /* otherwise look for driver information in config and build driver */
380 0 0         else if ((error = git_diff_driver_load(out, repo, values[0])) < 0) {
381 0 0         if (error == GIT_ENOTFOUND) {
382 0           error = 0;
383 0           git_error_clear();
384             }
385             }
386              
387 100 50         if (!*out)
388 100           *out = &diff_driver_auto;
389              
390 100           return error;
391             }
392              
393 0           void git_diff_driver_free(git_diff_driver *driver)
394             {
395             git_diff_driver_pattern *pat;
396              
397 0 0         if (!driver)
398 0           return;
399              
400 0 0         while ((pat = git_array_pop(driver->fn_patterns)) != NULL)
    0          
401 0           git_regexp_dispose(&pat->re);
402 0           git_array_clear(driver->fn_patterns);
403              
404 0           git_regexp_dispose(&driver->word_pattern);
405              
406 0           git__free(driver);
407             }
408              
409 100           void git_diff_driver_update_options(
410             uint32_t *option_flags, git_diff_driver *driver)
411             {
412 100 100         if ((*option_flags & FORCE_DIFFABLE) == 0)
413 96           *option_flags |= driver->binary_flags;
414              
415 100           *option_flags |= driver->other_flags;
416 100           }
417              
418 60           int git_diff_driver_content_is_binary(
419             git_diff_driver *driver, const char *content, size_t content_len)
420             {
421 60           git_str search = GIT_STR_INIT;
422              
423 60           GIT_UNUSED(driver);
424              
425 60           git_str_attach_notowned(&search, content,
426             min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL));
427              
428             /* TODO: provide encoding / binary detection callbacks that can
429             * be UTF-8 aware, etc. For now, instead of trying to be smart,
430             * let's just use the simple NUL-byte detection that core git uses.
431             */
432              
433             /* previously was: if (git_str_is_binary(&search)) */
434 60 50         if (git_str_contains_nul(&search))
435 0           return 1;
436              
437 60           return 0;
438             }
439              
440 0           static int diff_context_line__simple(
441             git_diff_driver *driver, git_str *line)
442             {
443 0           char firstch = line->ptr[0];
444 0           GIT_UNUSED(driver);
445 0 0         return (git__isalpha(firstch) || firstch == '_' || firstch == '$');
    0          
    0          
446             }
447              
448 0           static int diff_context_line__pattern_match(
449             git_diff_driver *driver, git_str *line)
450             {
451 0           size_t i, maxi = git_array_size(driver->fn_patterns);
452             git_regmatch pmatch[2];
453              
454 0 0         for (i = 0; i < maxi; ++i) {
455 0 0         git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i);
456              
457 0 0         if (!git_regexp_search(&pat->re, line->ptr, 2, pmatch)) {
458 0 0         if (pat->flags & REG_NEGATE)
459 0           return false;
460              
461             /* use pmatch data to trim line data */
462 0           i = (pmatch[1].start >= 0) ? 1 : 0;
463 0           git_str_consume(line, git_str_cstr(line) + pmatch[i].start);
464 0           git_str_truncate(line, pmatch[i].end - pmatch[i].start);
465 0           git_str_rtrim(line);
466              
467 0           return true;
468             }
469             }
470              
471 0           return false;
472             }
473              
474 0           static long diff_context_find(
475             const char *line,
476             long line_len,
477             char *out,
478             long out_size,
479             void *payload)
480             {
481 0           git_diff_find_context_payload *ctxt = payload;
482              
483 0 0         if (git_str_set(&ctxt->line, line, (size_t)line_len) < 0)
484 0           return -1;
485 0           git_str_rtrim(&ctxt->line);
486              
487 0 0         if (!ctxt->line.size)
488 0           return -1;
489              
490 0 0         if (!ctxt->match_line || !ctxt->match_line(ctxt->driver, &ctxt->line))
    0          
491 0           return -1;
492              
493 0 0         if (out_size > (long)ctxt->line.size)
494 0           out_size = (long)ctxt->line.size;
495 0           memcpy(out, ctxt->line.ptr, (size_t)out_size);
496              
497 0           return out_size;
498             }
499              
500 38           void git_diff_find_context_init(
501             git_diff_find_context_fn *findfn_out,
502             git_diff_find_context_payload *payload_out,
503             git_diff_driver *driver)
504             {
505 38 50         *findfn_out = driver ? diff_context_find : NULL;
506              
507 38           memset(payload_out, 0, sizeof(*payload_out));
508 38 50         if (driver) {
509 38           payload_out->driver = driver;
510 76           payload_out->match_line = (driver->type == DIFF_DRIVER_PATTERNLIST) ?
511 38 50         diff_context_line__pattern_match : diff_context_line__simple;
512 38           git_str_init(&payload_out->line, 0);
513             }
514 38           }
515              
516 38           void git_diff_find_context_clear(git_diff_find_context_payload *payload)
517             {
518 38 50         if (payload) {
519 38           git_str_dispose(&payload->line);
520 38           payload->driver = NULL;
521             }
522 38           }