2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
8 #include "diff_driver.h"
10 #include "git2/attr.h"
18 #include "repository.h"
22 DIFF_DRIVER_BINARY
= 1,
24 DIFF_DRIVER_PATTERNLIST
= 3,
30 } git_diff_driver_pattern
;
33 REG_NEGATE
= (1 << 15) /* get out of the way of existing flags */
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
;
41 git_array_t(git_diff_driver_pattern
) fn_patterns
;
42 git_regexp word_pattern
;
43 char name
[GIT_FLEX_ARRAY
];
48 struct git_diff_driver_registry
{
52 #define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY)
54 static git_diff_driver global_drivers
[3] = {
55 { DIFF_DRIVER_AUTO
, 0, 0, },
56 { DIFF_DRIVER_BINARY
, GIT_DIFF_FORCE_BINARY
, 0 },
57 { DIFF_DRIVER_TEXT
, GIT_DIFF_FORCE_TEXT
, 0 },
60 git_diff_driver_registry
*git_diff_driver_registry_new(void)
62 git_diff_driver_registry
*reg
=
63 git__calloc(1, sizeof(git_diff_driver_registry
));
67 if (git_strmap_new(®
->drivers
) < 0) {
68 git_diff_driver_registry_free(reg
);
75 void git_diff_driver_registry_free(git_diff_driver_registry
*reg
)
82 git_strmap_foreach_value(reg
->drivers
, drv
, git_diff_driver_free(drv
));
83 git_strmap_free(reg
->drivers
);
87 static int diff_driver_add_patterns(
88 git_diff_driver
*drv
, const char *regex_str
, int regex_flags
)
91 const char *scan
, *end
;
92 git_diff_driver_pattern
*pat
= NULL
;
93 git_buf buf
= GIT_BUF_INIT
;
95 for (scan
= regex_str
; scan
; scan
= end
) {
96 /* get pattern to fill in */
97 if ((pat
= git_array_alloc(drv
->fn_patterns
)) == NULL
) {
101 pat
->flags
= regex_flags
;
103 pat
->flags
|= REG_NEGATE
;
107 if ((end
= strchr(scan
, '\n')) != NULL
) {
108 error
= git_buf_set(&buf
, scan
, end
- scan
);
111 error
= git_buf_sets(&buf
, scan
);
116 if ((error
= git_regexp_compile(&pat
->re
, buf
.ptr
, regex_flags
)) != 0) {
118 * TODO: issue a warning
123 if (error
&& pat
!= NULL
)
124 (void)git_array_pop(drv
->fn_patterns
); /* release last item */
125 git_buf_dispose(&buf
);
127 /* We want to ignore bad patterns, so return success regardless */
131 static int diff_driver_xfuncname(const git_config_entry
*entry
, void *payload
)
133 return diff_driver_add_patterns(payload
, entry
->value
, 0);
136 static int diff_driver_funcname(const git_config_entry
*entry
, void *payload
)
138 return diff_driver_add_patterns(payload
, entry
->value
, 0);
141 static git_diff_driver_registry
*git_repository_driver_registry(
142 git_repository
*repo
)
144 git_diff_driver_registry
*reg
= git_atomic_load(repo
->diff_drivers
), *newreg
;
148 newreg
= git_diff_driver_registry_new();
150 git_error_set(GIT_ERROR_REPOSITORY
, "unable to create diff driver registry");
153 reg
= git_atomic_compare_and_swap(&repo
->diff_drivers
, NULL
, newreg
);
157 /* if we race, free losing allocation */
158 git_diff_driver_registry_free(newreg
);
163 static int diff_driver_alloc(
164 git_diff_driver
**out
, size_t *namelen_out
, const char *name
)
166 git_diff_driver
*driver
;
167 size_t driverlen
= sizeof(git_diff_driver
),
168 namelen
= strlen(name
),
171 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen
, driverlen
, namelen
);
172 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen
, alloclen
, 1);
174 driver
= git__calloc(1, alloclen
);
175 GIT_ERROR_CHECK_ALLOC(driver
);
177 memcpy(driver
->name
, name
, namelen
);
182 *namelen_out
= namelen
;
187 static int git_diff_driver_builtin(
188 git_diff_driver
**out
,
189 git_diff_driver_registry
*reg
,
190 const char *driver_name
)
192 git_diff_driver_definition
*ddef
= NULL
;
193 git_diff_driver
*drv
= NULL
;
197 for (idx
= 0; idx
< ARRAY_SIZE(builtin_defs
); ++idx
) {
198 if (!strcasecmp(driver_name
, builtin_defs
[idx
].name
)) {
199 ddef
= &builtin_defs
[idx
];
206 if ((error
= diff_driver_alloc(&drv
, NULL
, ddef
->name
)) < 0)
209 drv
->type
= DIFF_DRIVER_PATTERNLIST
;
212 (error
= diff_driver_add_patterns(
213 drv
, ddef
->fns
, ddef
->flags
)) < 0)
217 (error
= git_regexp_compile(&drv
->word_pattern
, ddef
->words
, ddef
->flags
)) < 0)
220 if ((error
= git_strmap_set(reg
->drivers
, drv
->name
, drv
)) < 0)
225 git_diff_driver_free(drv
);
232 static int git_diff_driver_load(
233 git_diff_driver
**out
, git_repository
*repo
, const char *driver_name
)
236 git_diff_driver_registry
*reg
;
237 git_diff_driver
*drv
;
239 git_config
*cfg
= NULL
;
240 git_buf name
= GIT_BUF_INIT
;
241 git_config_entry
*ce
= NULL
;
242 bool found_driver
= false;
244 if ((reg
= git_repository_driver_registry(repo
)) == NULL
)
247 if ((drv
= git_strmap_get(reg
->drivers
, driver_name
)) != NULL
) {
252 if ((error
= diff_driver_alloc(&drv
, &namelen
, driver_name
)) < 0)
255 drv
->type
= DIFF_DRIVER_AUTO
;
257 /* if you can't read config for repo, just use default driver */
258 if (git_repository_config_snapshot(&cfg
, repo
) < 0) {
263 if ((error
= git_buf_printf(&name
, "diff.%s.binary", driver_name
)) < 0)
266 switch (git_config__get_bool_force(cfg
, name
.ptr
, -1)) {
268 /* if diff.<driver>.binary is true, just return the binary driver */
269 *out
= &global_drivers
[DIFF_DRIVER_BINARY
];
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
;
278 /* diff.<driver>.binary unspecified or "auto", so just continue */
282 /* TODO: warn if diff.<name>.command or diff.<name>.textconv are set */
284 git_buf_truncate(&name
, namelen
+ strlen("diff.."));
285 if ((error
= git_buf_PUTS(&name
, "xfuncname")) < 0)
288 if ((error
= git_config_get_multivar_foreach(
289 cfg
, name
.ptr
, NULL
, diff_driver_xfuncname
, drv
)) < 0) {
290 if (error
!= GIT_ENOTFOUND
)
292 git_error_clear(); /* no diff.<driver>.xfuncname, so just continue */
295 git_buf_truncate(&name
, namelen
+ strlen("diff.."));
296 if ((error
= git_buf_PUTS(&name
, "funcname")) < 0)
299 if ((error
= git_config_get_multivar_foreach(
300 cfg
, name
.ptr
, NULL
, diff_driver_funcname
, drv
)) < 0) {
301 if (error
!= GIT_ENOTFOUND
)
303 git_error_clear(); /* no diff.<driver>.funcname, so just continue */
306 /* if we found any patterns, set driver type to use correct callback */
307 if (git_array_size(drv
->fn_patterns
) > 0) {
308 drv
->type
= DIFF_DRIVER_PATTERNLIST
;
312 git_buf_truncate(&name
, namelen
+ strlen("diff.."));
313 if ((error
= git_buf_PUTS(&name
, "wordregex")) < 0)
316 if ((error
= git_config__lookup_entry(&ce
, cfg
, name
.ptr
, false)) < 0)
318 if (!ce
|| !ce
->value
)
319 /* no diff.<driver>.wordregex, so just continue */;
320 else if (!(error
= git_regexp_compile(&drv
->word_pattern
, ce
->value
, 0)))
323 /* TODO: warn about bad regex instead of failure */
327 /* TODO: look up diff.<driver>.algorithm to turn on minimal / patience
328 * diff in drv->other_flags
331 /* if no driver config found at all, fall back on AUTO driver */
335 /* store driver in registry */
336 if ((error
= git_strmap_set(reg
->drivers
, drv
->name
, drv
)) < 0)
342 git_config_entry_free(ce
);
343 git_buf_dispose(&name
);
344 git_config_free(cfg
);
347 int error2
= git_diff_driver_builtin(out
, reg
, driver_name
);
352 if (drv
&& drv
!= *out
)
353 git_diff_driver_free(drv
);
358 int git_diff_driver_lookup(
359 git_diff_driver
**out
, git_repository
*repo
,
360 git_attr_session
*attrsession
, const char *path
)
363 const char *values
[1], *attrs
[] = { "diff" };
368 if (!repo
|| !path
|| !strlen(path
))
369 /* just use the auto value */;
370 else if ((error
= git_attr_get_many_with_session(values
, repo
,
371 attrsession
, 0, path
, 1, attrs
)) < 0)
372 /* return error below */;
374 else if (GIT_ATTR_IS_UNSPECIFIED(values
[0]))
375 /* just use the auto value */;
376 else if (GIT_ATTR_IS_FALSE(values
[0]))
377 *out
= &global_drivers
[DIFF_DRIVER_BINARY
];
378 else if (GIT_ATTR_IS_TRUE(values
[0]))
379 *out
= &global_drivers
[DIFF_DRIVER_TEXT
];
381 /* otherwise look for driver information in config and build driver */
382 else if ((error
= git_diff_driver_load(out
, repo
, values
[0])) < 0) {
383 if (error
== GIT_ENOTFOUND
) {
390 *out
= &global_drivers
[DIFF_DRIVER_AUTO
];
395 void git_diff_driver_free(git_diff_driver
*driver
)
397 git_diff_driver_pattern
*pat
;
402 while ((pat
= git_array_pop(driver
->fn_patterns
)) != NULL
)
403 git_regexp_dispose(&pat
->re
);
404 git_array_clear(driver
->fn_patterns
);
406 git_regexp_dispose(&driver
->word_pattern
);
411 void git_diff_driver_update_options(
412 uint32_t *option_flags
, git_diff_driver
*driver
)
414 if ((*option_flags
& FORCE_DIFFABLE
) == 0)
415 *option_flags
|= driver
->binary_flags
;
417 *option_flags
|= driver
->other_flags
;
420 int git_diff_driver_content_is_binary(
421 git_diff_driver
*driver
, const char *content
, size_t content_len
)
423 git_buf search
= GIT_BUF_INIT
;
427 git_buf_attach_notowned(&search
, content
,
428 min(content_len
, GIT_FILTER_BYTES_TO_CHECK_NUL
));
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.
435 /* previously was: if (git_buf_is_binary(&search)) */
436 if (git_buf_contains_nul(&search
))
442 static int diff_context_line__simple(
443 git_diff_driver
*driver
, git_buf
*line
)
445 char firstch
= line
->ptr
[0];
447 return (git__isalpha(firstch
) || firstch
== '_' || firstch
== '$');
450 static int diff_context_line__pattern_match(
451 git_diff_driver
*driver
, git_buf
*line
)
453 size_t i
, maxi
= git_array_size(driver
->fn_patterns
);
454 git_regmatch pmatch
[2];
456 for (i
= 0; i
< maxi
; ++i
) {
457 git_diff_driver_pattern
*pat
= git_array_get(driver
->fn_patterns
, i
);
459 if (!git_regexp_search(&pat
->re
, line
->ptr
, 2, pmatch
)) {
460 if (pat
->flags
& REG_NEGATE
)
463 /* use pmatch data to trim line data */
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
);
476 static long diff_context_find(
483 git_diff_find_context_payload
*ctxt
= payload
;
485 if (git_buf_set(&ctxt
->line
, line
, (size_t)line_len
) < 0)
487 git_buf_rtrim(&ctxt
->line
);
489 if (!ctxt
->line
.size
)
492 if (!ctxt
->match_line
|| !ctxt
->match_line(ctxt
->driver
, &ctxt
->line
))
495 if (out_size
> (long)ctxt
->line
.size
)
496 out_size
= (long)ctxt
->line
.size
;
497 memcpy(out
, ctxt
->line
.ptr
, (size_t)out_size
);
502 void 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
)
507 *findfn_out
= driver
? diff_context_find
: NULL
;
509 memset(payload_out
, 0, sizeof(*payload_out
));
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);
518 void git_diff_find_context_clear(git_diff_find_context_payload
*payload
)
521 git_buf_dispose(&payload
->line
);
522 payload
->driver
= NULL
;