]> git.proxmox.com Git - libgit2.git/blob - src/diff_driver.c
New upstream version 1.4.3+dfsg.1
[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
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 git_diff_driver_registry *git_diff_driver_registry_new(void)
59 {
60 git_diff_driver_registry *reg =
61 git__calloc(1, sizeof(git_diff_driver_registry));
62 if (!reg)
63 return NULL;
64
65 if (git_strmap_new(&reg->drivers) < 0) {
66 git_diff_driver_registry_free(reg);
67 return NULL;
68 }
69
70 return reg;
71 }
72
73 void git_diff_driver_registry_free(git_diff_driver_registry *reg)
74 {
75 git_diff_driver *drv;
76
77 if (!reg)
78 return;
79
80 git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv));
81 git_strmap_free(reg->drivers);
82 git__free(reg);
83 }
84
85 static int diff_driver_add_patterns(
86 git_diff_driver *drv, const char *regex_str, int regex_flags)
87 {
88 int error = 0;
89 const char *scan, *end;
90 git_diff_driver_pattern *pat = NULL;
91 git_str buf = GIT_STR_INIT;
92
93 for (scan = regex_str; scan; scan = end) {
94 /* get pattern to fill in */
95 if ((pat = git_array_alloc(drv->fn_patterns)) == NULL) {
96 return -1;
97 }
98
99 pat->flags = regex_flags;
100 if (*scan == '!') {
101 pat->flags |= REG_NEGATE;
102 ++scan;
103 }
104
105 if ((end = strchr(scan, '\n')) != NULL) {
106 error = git_str_set(&buf, scan, end - scan);
107 end++;
108 } else {
109 error = git_str_sets(&buf, scan);
110 }
111 if (error < 0)
112 break;
113
114 if ((error = git_regexp_compile(&pat->re, buf.ptr, regex_flags)) != 0) {
115 /*
116 * TODO: issue a warning
117 */
118 }
119 }
120
121 if (error && pat != NULL)
122 (void)git_array_pop(drv->fn_patterns); /* release last item */
123 git_str_dispose(&buf);
124
125 /* We want to ignore bad patterns, so return success regardless */
126 return 0;
127 }
128
129 static int diff_driver_xfuncname(const git_config_entry *entry, void *payload)
130 {
131 return diff_driver_add_patterns(payload, entry->value, 0);
132 }
133
134 static int diff_driver_funcname(const git_config_entry *entry, void *payload)
135 {
136 return diff_driver_add_patterns(payload, entry->value, 0);
137 }
138
139 static git_diff_driver_registry *git_repository_driver_registry(
140 git_repository *repo)
141 {
142 git_diff_driver_registry *reg = git_atomic_load(repo->diff_drivers), *newreg;
143 if (reg)
144 return reg;
145
146 newreg = git_diff_driver_registry_new();
147 if (!newreg) {
148 git_error_set(GIT_ERROR_REPOSITORY, "unable to create diff driver registry");
149 return newreg;
150 }
151 reg = git_atomic_compare_and_swap(&repo->diff_drivers, NULL, newreg);
152 if (!reg) {
153 reg = newreg;
154 } else {
155 /* if we race, free losing allocation */
156 git_diff_driver_registry_free(newreg);
157 }
158 return reg;
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 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, driverlen, namelen);
170 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
171
172 driver = git__calloc(1, alloclen);
173 GIT_ERROR_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 git_diff_driver_definition *ddef = NULL;
191 git_diff_driver *drv = NULL;
192 int error = 0;
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)) < 0)
212 goto done;
213
214 if (ddef->words &&
215 (error = git_regexp_compile(&drv->word_pattern, ddef->words, ddef->flags)) < 0)
216 goto done;
217
218 if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0)
219 goto done;
220
221 done:
222 if (error && drv)
223 git_diff_driver_free(drv);
224 else
225 *out = drv;
226
227 return error;
228 }
229
230 static int git_diff_driver_load(
231 git_diff_driver **out, git_repository *repo, const char *driver_name)
232 {
233 int error = 0;
234 git_diff_driver_registry *reg;
235 git_diff_driver *drv;
236 size_t namelen;
237 git_config *cfg = NULL;
238 git_str name = GIT_STR_INIT;
239 git_config_entry *ce = NULL;
240 bool found_driver = false;
241
242 if ((reg = git_repository_driver_registry(repo)) == NULL)
243 return -1;
244
245 if ((drv = git_strmap_get(reg->drivers, driver_name)) != NULL) {
246 *out = drv;
247 return 0;
248 }
249
250 if ((error = diff_driver_alloc(&drv, &namelen, driver_name)) < 0)
251 goto done;
252
253 drv->type = DIFF_DRIVER_AUTO;
254
255 /* if you can't read config for repo, just use default driver */
256 if (git_repository_config_snapshot(&cfg, repo) < 0) {
257 git_error_clear();
258 goto done;
259 }
260
261 if ((error = git_str_printf(&name, "diff.%s.binary", driver_name)) < 0)
262 goto done;
263
264 switch (git_config__get_bool_force(cfg, name.ptr, -1)) {
265 case true:
266 /* if diff.<driver>.binary is true, just return the binary driver */
267 *out = &diff_driver_binary;
268 goto done;
269 case false:
270 /* if diff.<driver>.binary is false, force binary checks off */
271 /* but still may have custom function context patterns, etc. */
272 drv->binary_flags = GIT_DIFF_FORCE_TEXT;
273 found_driver = true;
274 break;
275 default:
276 /* diff.<driver>.binary unspecified or "auto", so just continue */
277 break;
278 }
279
280 /* TODO: warn if diff.<name>.command or diff.<name>.textconv are set */
281
282 git_str_truncate(&name, namelen + strlen("diff.."));
283 if ((error = git_str_PUTS(&name, "xfuncname")) < 0)
284 goto done;
285
286 if ((error = git_config_get_multivar_foreach(
287 cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) {
288 if (error != GIT_ENOTFOUND)
289 goto done;
290 git_error_clear(); /* no diff.<driver>.xfuncname, so just continue */
291 }
292
293 git_str_truncate(&name, namelen + strlen("diff.."));
294 if ((error = git_str_PUTS(&name, "funcname")) < 0)
295 goto done;
296
297 if ((error = git_config_get_multivar_foreach(
298 cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) {
299 if (error != GIT_ENOTFOUND)
300 goto done;
301 git_error_clear(); /* no diff.<driver>.funcname, so just continue */
302 }
303
304 /* if we found any patterns, set driver type to use correct callback */
305 if (git_array_size(drv->fn_patterns) > 0) {
306 drv->type = DIFF_DRIVER_PATTERNLIST;
307 found_driver = true;
308 }
309
310 git_str_truncate(&name, namelen + strlen("diff.."));
311 if ((error = git_str_PUTS(&name, "wordregex")) < 0)
312 goto done;
313
314 if ((error = git_config__lookup_entry(&ce, cfg, name.ptr, false)) < 0)
315 goto done;
316 if (!ce || !ce->value)
317 /* no diff.<driver>.wordregex, so just continue */;
318 else if (!(error = git_regexp_compile(&drv->word_pattern, ce->value, 0)))
319 found_driver = true;
320 else {
321 /* TODO: warn about bad regex instead of failure */
322 goto done;
323 }
324
325 /* TODO: look up diff.<driver>.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 if (!found_driver)
331 goto done;
332
333 /* store driver in registry */
334 if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0)
335 goto done;
336
337 *out = drv;
338
339 done:
340 git_config_entry_free(ce);
341 git_str_dispose(&name);
342 git_config_free(cfg);
343
344 if (!*out) {
345 int error2 = git_diff_driver_builtin(out, reg, driver_name);
346 if (!error)
347 error = error2;
348 }
349
350 if (drv && drv != *out)
351 git_diff_driver_free(drv);
352
353 return error;
354 }
355
356 int git_diff_driver_lookup(
357 git_diff_driver **out, git_repository *repo,
358 git_attr_session *attrsession, const char *path)
359 {
360 int error = 0;
361 const char *values[1], *attrs[] = { "diff" };
362
363 GIT_ASSERT_ARG(out);
364 *out = NULL;
365
366 if (!repo || !path || !strlen(path))
367 /* just use the auto value */;
368 else if ((error = git_attr_get_many_with_session(values, repo,
369 attrsession, 0, path, 1, attrs)) < 0)
370 /* return error below */;
371
372 else if (GIT_ATTR_IS_UNSPECIFIED(values[0]))
373 /* just use the auto value */;
374 else if (GIT_ATTR_IS_FALSE(values[0]))
375 *out = &diff_driver_binary;
376 else if (GIT_ATTR_IS_TRUE(values[0]))
377 *out = &diff_driver_text;
378
379 /* otherwise look for driver information in config and build driver */
380 else if ((error = git_diff_driver_load(out, repo, values[0])) < 0) {
381 if (error == GIT_ENOTFOUND) {
382 error = 0;
383 git_error_clear();
384 }
385 }
386
387 if (!*out)
388 *out = &diff_driver_auto;
389
390 return error;
391 }
392
393 void git_diff_driver_free(git_diff_driver *driver)
394 {
395 git_diff_driver_pattern *pat;
396
397 if (!driver)
398 return;
399
400 while ((pat = git_array_pop(driver->fn_patterns)) != NULL)
401 git_regexp_dispose(&pat->re);
402 git_array_clear(driver->fn_patterns);
403
404 git_regexp_dispose(&driver->word_pattern);
405
406 git__free(driver);
407 }
408
409 void git_diff_driver_update_options(
410 uint32_t *option_flags, git_diff_driver *driver)
411 {
412 if ((*option_flags & FORCE_DIFFABLE) == 0)
413 *option_flags |= driver->binary_flags;
414
415 *option_flags |= driver->other_flags;
416 }
417
418 int git_diff_driver_content_is_binary(
419 git_diff_driver *driver, const char *content, size_t content_len)
420 {
421 git_str search = GIT_STR_INIT;
422
423 GIT_UNUSED(driver);
424
425 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 if (git_str_contains_nul(&search))
435 return 1;
436
437 return 0;
438 }
439
440 static int diff_context_line__simple(
441 git_diff_driver *driver, git_str *line)
442 {
443 char firstch = line->ptr[0];
444 GIT_UNUSED(driver);
445 return (git__isalpha(firstch) || firstch == '_' || firstch == '$');
446 }
447
448 static int diff_context_line__pattern_match(
449 git_diff_driver *driver, git_str *line)
450 {
451 size_t i, maxi = git_array_size(driver->fn_patterns);
452 git_regmatch pmatch[2];
453
454 for (i = 0; i < maxi; ++i) {
455 git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i);
456
457 if (!git_regexp_search(&pat->re, line->ptr, 2, pmatch)) {
458 if (pat->flags & REG_NEGATE)
459 return false;
460
461 /* use pmatch data to trim line data */
462 i = (pmatch[1].start >= 0) ? 1 : 0;
463 git_str_consume(line, git_str_cstr(line) + pmatch[i].start);
464 git_str_truncate(line, pmatch[i].end - pmatch[i].start);
465 git_str_rtrim(line);
466
467 return true;
468 }
469 }
470
471 return false;
472 }
473
474 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 git_diff_find_context_payload *ctxt = payload;
482
483 if (git_str_set(&ctxt->line, line, (size_t)line_len) < 0)
484 return -1;
485 git_str_rtrim(&ctxt->line);
486
487 if (!ctxt->line.size)
488 return -1;
489
490 if (!ctxt->match_line || !ctxt->match_line(ctxt->driver, &ctxt->line))
491 return -1;
492
493 if (out_size > (long)ctxt->line.size)
494 out_size = (long)ctxt->line.size;
495 memcpy(out, ctxt->line.ptr, (size_t)out_size);
496
497 return out_size;
498 }
499
500 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 *findfn_out = driver ? diff_context_find : NULL;
506
507 memset(payload_out, 0, sizeof(*payload_out));
508 if (driver) {
509 payload_out->driver = driver;
510 payload_out->match_line = (driver->type == DIFF_DRIVER_PATTERNLIST) ?
511 diff_context_line__pattern_match : diff_context_line__simple;
512 git_str_init(&payload_out->line, 0);
513 }
514 }
515
516 void git_diff_find_context_clear(git_diff_find_context_payload *payload)
517 {
518 if (payload) {
519 git_str_dispose(&payload->line);
520 payload->driver = NULL;
521 }
522 }