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