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