]> git.proxmox.com Git - libgit2.git/blame - src/diff_driver.c
Remove swp files
[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 */
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"
114f5a6c
RB
15#include "map.h"
16#include "buf_text.h"
9f77b3f6 17#include "config.h"
5dc98298 18#include "repository.h"
114f5a6c 19
c8e02b87 20GIT__USE_STRMAP
3eadfecd 21
114f5a6c
RB
22typedef enum {
23 DIFF_DRIVER_AUTO = 0,
5dc98298
RB
24 DIFF_DRIVER_BINARY = 1,
25 DIFF_DRIVER_TEXT = 2,
26 DIFF_DRIVER_PATTERNLIST = 3,
114f5a6c
RB
27} git_diff_driver_t;
28
a5a38643
RB
29typedef struct {
30 regex_t re;
31 int flags;
32} git_diff_driver_pattern;
33
114f5a6c 34enum {
a5a38643 35 REG_NEGATE = (1 << 15) /* get out of the way of existing flags */
114f5a6c
RB
36};
37
38/* data for finding function context for a given file type */
39struct git_diff_driver {
40 git_diff_driver_t type;
5dc98298
RB
41 uint32_t binary_flags;
42 uint32_t other_flags;
a5a38643 43 git_array_t(git_diff_driver_pattern) fn_patterns;
5dc98298 44 regex_t word_pattern;
42e6cf78 45 char name[GIT_FLEX_ARRAY];
114f5a6c
RB
46};
47
c7c260a5 48#include "userdiff.h"
2c65602e 49
114f5a6c
RB
50struct git_diff_driver_registry {
51 git_strmap *drivers;
114f5a6c
RB
52};
53
5dc98298
RB
54#define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY)
55
114f5a6c 56static git_diff_driver global_drivers[3] = {
5dc98298
RB
57 { DIFF_DRIVER_AUTO, 0, 0, },
58 { DIFF_DRIVER_BINARY, GIT_DIFF_FORCE_BINARY, 0 },
59 { DIFF_DRIVER_TEXT, GIT_DIFF_FORCE_TEXT, 0 },
114f5a6c
RB
60};
61
62git_diff_driver_registry *git_diff_driver_registry_new()
63{
3eadfecd
RB
64 git_diff_driver_registry *reg =
65 git__calloc(1, sizeof(git_diff_driver_registry));
66 if (!reg)
67 return NULL;
68
40ed4990 69 if (git_strmap_alloc(&reg->drivers) < 0) {
3eadfecd
RB
70 git_diff_driver_registry_free(reg);
71 return NULL;
72 }
73
74 return reg;
114f5a6c
RB
75}
76
77void git_diff_driver_registry_free(git_diff_driver_registry *reg)
78{
5dc98298
RB
79 git_diff_driver *drv;
80
3eadfecd
RB
81 if (!reg)
82 return;
83
5dc98298 84 git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv));
3eadfecd 85 git_strmap_free(reg->drivers);
114f5a6c
RB
86 git__free(reg);
87}
88
a5a38643
RB
89static int diff_driver_add_patterns(
90 git_diff_driver *drv, const char *regex_str, int regex_flags)
5dc98298 91{
a5a38643
RB
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 }
5dc98298 103
a5a38643
RB
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 }
5dc98298
RB
125 }
126
a5a38643
RB
127 if (error && pat != NULL)
128 (void)git_array_pop(drv->fn_patterns); /* release last item */
129 git_buf_free(&buf);
5dc98298 130
a5a38643 131 return error;
5dc98298
RB
132}
133
134static int diff_driver_xfuncname(const git_config_entry *entry, void *payload)
135{
a5a38643 136 return diff_driver_add_patterns(payload, entry->value, REG_EXTENDED);
5dc98298
RB
137}
138
139static int diff_driver_funcname(const git_config_entry *entry, void *payload)
140{
a5a38643 141 return diff_driver_add_patterns(payload, entry->value, 0);
5dc98298
RB
142}
143
144static 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
392702ee
ET
161static 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),
f1453c59
ET
166 namelen = strlen(name),
167 alloclen;
392702ee 168
f1453c59
ET
169 GITERR_CHECK_ALLOC_ADD(&alloclen, driverlen, namelen);
170 GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
392702ee 171
f1453c59 172 driver = git__calloc(1, alloclen);
392702ee
ET
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
a5a38643
RB
185static 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;
392702ee 193 size_t idx;
a5a38643
RB
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
392702ee
ET
204 if ((error = diff_driver_alloc(&drv, NULL, ddef->name)) < 0)
205 goto done;
a5a38643
RB
206
207 drv->type = DIFF_DRIVER_PATTERNLIST;
a5a38643
RB
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);
2c65602e
RB
223 if (error > 0)
224 error = 0;
a5a38643
RB
225
226done:
2c65602e 227 if (error && drv)
a5a38643 228 git_diff_driver_free(drv);
2c65602e 229 else
a5a38643 230 *out = drv;
a5a38643
RB
231
232 return error;
233}
234
3eadfecd 235static int git_diff_driver_load(
5dc98298 236 git_diff_driver **out, git_repository *repo, const char *driver_name)
3eadfecd 237{
9f77b3f6 238 int error = 0;
5dc98298 239 git_diff_driver_registry *reg;
a5a38643 240 git_diff_driver *drv = NULL;
392702ee 241 size_t namelen;
42e6cf78 242 khiter_t pos;
392702ee 243 git_config *cfg = NULL;
5dc98298 244 git_buf name = GIT_BUF_INIT;
9f77b3f6 245 const git_config_entry *ce;
42e6cf78 246 bool found_driver = false;
5dc98298 247
a5a38643 248 if ((reg = git_repository_driver_registry(repo)) == NULL)
5dc98298 249 return -1;
a5a38643
RB
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;
5dc98298
RB
255 }
256
392702ee
ET
257 if ((error = diff_driver_alloc(&drv, &namelen, driver_name)) < 0)
258 goto done;
259
a37aa82e 260 drv->type = DIFF_DRIVER_AUTO;
a37aa82e 261
5dc98298 262 /* if you can't read config for repo, just use default driver */
ac99d86b 263 if (git_repository_config_snapshot(&cfg, repo) < 0) {
5dc98298 264 giterr_clear();
a5a38643 265 goto done;
5dc98298
RB
266 }
267
5dc98298 268 if ((error = git_buf_printf(&name, "diff.%s.binary", driver_name)) < 0)
54faddd2 269 goto done;
9f77b3f6
RB
270
271 switch (git_config__get_bool_force(cfg, name.ptr, -1)) {
272 case true:
5dc98298 273 /* if diff.<driver>.binary is true, just return the binary driver */
5dc98298 274 *out = &global_drivers[DIFF_DRIVER_BINARY];
54faddd2 275 goto done;
9f77b3f6 276 case false:
5dc98298
RB
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;
42e6cf78 280 found_driver = true;
9f77b3f6
RB
281 break;
282 default:
a5a38643 283 /* diff.<driver>.binary unspecified or "auto", so just continue */
9f77b3f6 284 break;
5dc98298
RB
285 }
286
287 /* TODO: warn if diff.<name>.command or diff.<name>.textconv are set */
288
42e6cf78
RB
289 git_buf_truncate(&name, namelen + strlen("diff.."));
290 git_buf_put(&name, "xfuncname", strlen("xfuncname"));
4efa3290 291 if ((error = git_config_get_multivar_foreach(
5dc98298
RB
292 cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) {
293 if (error != GIT_ENOTFOUND)
54faddd2 294 goto done;
42e6cf78 295 giterr_clear(); /* no diff.<driver>.xfuncname, so just continue */
5dc98298 296 }
3eadfecd 297
42e6cf78
RB
298 git_buf_truncate(&name, namelen + strlen("diff.."));
299 git_buf_put(&name, "funcname", strlen("funcname"));
4efa3290 300 if ((error = git_config_get_multivar_foreach(
5dc98298
RB
301 cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) {
302 if (error != GIT_ENOTFOUND)
54faddd2 303 goto done;
42e6cf78 304 giterr_clear(); /* no diff.<driver>.funcname, so just continue */
5dc98298
RB
305 }
306
307 /* if we found any patterns, set driver type to use correct callback */
42e6cf78 308 if (git_array_size(drv->fn_patterns) > 0) {
5dc98298 309 drv->type = DIFF_DRIVER_PATTERNLIST;
42e6cf78
RB
310 found_driver = true;
311 }
5dc98298 312
42e6cf78
RB
313 git_buf_truncate(&name, namelen + strlen("diff.."));
314 git_buf_put(&name, "wordregex", strlen("wordregex"));
9f77b3f6 315 if ((error = git_config__lookup_entry(&ce, cfg, name.ptr, false)) < 0)
54faddd2 316 goto done;
9f77b3f6
RB
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)))
42e6cf78 320 found_driver = true;
9f77b3f6
RB
321 else {
322 /* TODO: warn about bad regex instead of failure */
323 error = giterr_set_regex(&drv->word_pattern, error);
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 */
336 git_strmap_insert(reg->drivers, drv->name, drv, error);
337 if (error < 0)
54faddd2 338 goto done;
2c65602e 339 error = 0;
42e6cf78 340
5dc98298 341 *out = drv;
5dc98298 342
54faddd2
RB
343done:
344 git_buf_free(&name);
29c4cb09 345 git_config_free(cfg);
54faddd2 346
a5a38643
RB
347 if (!*out) {
348 int error2 = git_diff_driver_builtin(out, reg, driver_name);
349 if (!error)
350 error = error2;
351 }
54faddd2
RB
352
353 if (drv && drv != *out)
354 git_diff_driver_free(drv);
355
5dc98298 356 return error;
3eadfecd
RB
357}
358
114f5a6c
RB
359int git_diff_driver_lookup(
360 git_diff_driver **out, git_repository *repo, const char *path)
361{
3eadfecd 362 int error = 0;
114f5a6c
RB
363 const char *value;
364
365 assert(out);
2c65602e 366 *out = NULL;
114f5a6c
RB
367
368 if (!repo || !path || !strlen(path))
2c65602e
RB
369 /* just use the auto value */;
370 else if ((error = git_attr_get(&value, repo, 0, path, "diff")) < 0)
371 /* return error below */;
372 else if (GIT_ATTR_UNSPECIFIED(value))
42e6cf78
RB
373 /* just use the auto value */;
374 else if (GIT_ATTR_FALSE(value))
5dc98298 375 *out = &global_drivers[DIFF_DRIVER_BINARY];
42e6cf78 376 else if (GIT_ATTR_TRUE(value))
5dc98298 377 *out = &global_drivers[DIFF_DRIVER_TEXT];
114f5a6c
RB
378
379 /* otherwise look for driver information in config and build driver */
42e6cf78 380 else if ((error = git_diff_driver_load(out, repo, value)) < 0) {
2c65602e
RB
381 if (error == GIT_ENOTFOUND) {
382 error = 0;
3eadfecd 383 giterr_clear();
2c65602e 384 }
3eadfecd 385 }
114f5a6c 386
42e6cf78
RB
387 if (!*out)
388 *out = &global_drivers[DIFF_DRIVER_AUTO];
389
2c65602e 390 return error;
114f5a6c
RB
391}
392
393void git_diff_driver_free(git_diff_driver *driver)
394{
5dc98298
RB
395 size_t i;
396
397 if (!driver)
398 return;
399
54faddd2 400 for (i = 0; i < git_array_size(driver->fn_patterns); ++i)
a5a38643 401 regfree(& git_array_get(driver->fn_patterns, i)->re);
5dc98298
RB
402 git_array_clear(driver->fn_patterns);
403
404 regfree(&driver->word_pattern);
405
406 git__free(driver);
114f5a6c
RB
407}
408
5dc98298
RB
409void git_diff_driver_update_options(
410 uint32_t *option_flags, git_diff_driver *driver)
114f5a6c 411{
5dc98298
RB
412 if ((*option_flags & FORCE_DIFFABLE) == 0)
413 *option_flags |= driver->binary_flags;
414
415 *option_flags |= driver->other_flags;
114f5a6c
RB
416}
417
418int git_diff_driver_content_is_binary(
419 git_diff_driver *driver, const char *content, size_t content_len)
420{
d4cf1675 421 git_buf search = GIT_BUF_INIT;
114f5a6c
RB
422
423 GIT_UNUSED(driver);
424
d4cf1675
ET
425 git_buf_attach_notowned(&search, content,
426 min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL));
427
114f5a6c
RB
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_buf_text_is_binary(&search)) */
434 if (git_buf_text_contains_nul(&search))
435 return 1;
436
437 return 0;
438}
439
5dc98298 440static int diff_context_line__simple(
a5a38643 441 git_diff_driver *driver, git_buf *line)
5dc98298 442{
a5a38643 443 char firstch = line->ptr[0];
5dc98298 444 GIT_UNUSED(driver);
a5a38643 445 return (git__isalpha(firstch) || firstch == '_' || firstch == '$');
5dc98298
RB
446}
447
448static int diff_context_line__pattern_match(
a5a38643 449 git_diff_driver *driver, git_buf *line)
5dc98298 450{
b8e86c62 451 size_t i, maxi = git_array_size(driver->fn_patterns);
a5a38643 452 regmatch_t pmatch[2];
5dc98298 453
b8e86c62 454 for (i = 0; i < maxi; ++i) {
a5a38643
RB
455 git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i);
456
457 if (!regexec(&pat->re, line->ptr, 2, pmatch, 0)) {
458 if (pat->flags & REG_NEGATE)
459 return false;
b8e86c62
RB
460
461 /* use pmatch data to trim line data */
462 i = (pmatch[1].rm_so >= 0) ? 1 : 0;
463 git_buf_consume(line, git_buf_cstr(line) + pmatch[i].rm_so);
464 git_buf_truncate(line, pmatch[i].rm_eo - pmatch[i].rm_so);
082e82db 465 git_buf_rtrim(line);
b8e86c62 466
5dc98298 467 return true;
a5a38643 468 }
5dc98298
RB
469 }
470
471 return false;
472}
473
114f5a6c
RB
474static long diff_context_find(
475 const char *line,
476 long line_len,
477 char *out,
478 long out_size,
479 void *payload)
480{
5dc98298 481 git_diff_find_context_payload *ctxt = payload;
114f5a6c 482
5dc98298 483 if (git_buf_set(&ctxt->line, line, (size_t)line_len) < 0)
114f5a6c 484 return -1;
5dc98298 485 git_buf_rtrim(&ctxt->line);
114f5a6c 486
5dc98298 487 if (!ctxt->line.size)
114f5a6c
RB
488 return -1;
489
a5a38643 490 if (!ctxt->match_line || !ctxt->match_line(ctxt->driver, &ctxt->line))
5dc98298 491 return -1;
114f5a6c 492
a5f9b5f8 493 if (out_size > (long)ctxt->line.size)
584f2d30 494 out_size = (long)ctxt->line.size;
a5f9b5f8 495 memcpy(out, ctxt->line.ptr, (size_t)out_size);
114f5a6c 496
a5f9b5f8 497 return out_size;
5dc98298 498}
114f5a6c 499
5dc98298
RB
500void 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_buf_init(&payload_out->line, 0);
513 }
114f5a6c
RB
514}
515
5dc98298 516void git_diff_find_context_clear(git_diff_find_context_payload *payload)
114f5a6c 517{
5dc98298
RB
518 if (payload) {
519 git_buf_free(&payload->line);
520 payload->driver = NULL;
521 }
114f5a6c
RB
522}
523