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