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