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 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 };
58 git_diff_driver_registry
*git_diff_driver_registry_new(void)
60 git_diff_driver_registry
*reg
=
61 git__calloc(1, sizeof(git_diff_driver_registry
));
65 if (git_strmap_new(®
->drivers
) < 0) {
66 git_diff_driver_registry_free(reg
);
73 void git_diff_driver_registry_free(git_diff_driver_registry
*reg
)
80 git_strmap_foreach_value(reg
->drivers
, drv
, git_diff_driver_free(drv
));
81 git_strmap_free(reg
->drivers
);
85 static int diff_driver_add_patterns(
86 git_diff_driver
*drv
, const char *regex_str
, int regex_flags
)
89 const char *scan
, *end
;
90 git_diff_driver_pattern
*pat
= NULL
;
91 git_str buf
= GIT_STR_INIT
;
93 for (scan
= regex_str
; scan
; scan
= end
) {
94 /* get pattern to fill in */
95 if ((pat
= git_array_alloc(drv
->fn_patterns
)) == NULL
) {
99 pat
->flags
= regex_flags
;
101 pat
->flags
|= REG_NEGATE
;
105 if ((end
= strchr(scan
, '\n')) != NULL
) {
106 error
= git_str_set(&buf
, scan
, end
- scan
);
109 error
= git_str_sets(&buf
, scan
);
114 if ((error
= git_regexp_compile(&pat
->re
, buf
.ptr
, regex_flags
)) != 0) {
116 * TODO: issue a warning
121 if (error
&& pat
!= NULL
)
122 (void)git_array_pop(drv
->fn_patterns
); /* release last item */
123 git_str_dispose(&buf
);
125 /* We want to ignore bad patterns, so return success regardless */
129 static int diff_driver_xfuncname(const git_config_entry
*entry
, void *payload
)
131 return diff_driver_add_patterns(payload
, entry
->value
, 0);
134 static int diff_driver_funcname(const git_config_entry
*entry
, void *payload
)
136 return diff_driver_add_patterns(payload
, entry
->value
, 0);
139 static git_diff_driver_registry
*git_repository_driver_registry(
140 git_repository
*repo
)
142 git_diff_driver_registry
*reg
= git_atomic_load(repo
->diff_drivers
), *newreg
;
146 newreg
= git_diff_driver_registry_new();
148 git_error_set(GIT_ERROR_REPOSITORY
, "unable to create diff driver registry");
151 reg
= git_atomic_compare_and_swap(&repo
->diff_drivers
, NULL
, newreg
);
155 /* if we race, free losing allocation */
156 git_diff_driver_registry_free(newreg
);
161 static int diff_driver_alloc(
162 git_diff_driver
**out
, size_t *namelen_out
, const char *name
)
164 git_diff_driver
*driver
;
165 size_t driverlen
= sizeof(git_diff_driver
),
166 namelen
= strlen(name
),
169 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen
, driverlen
, namelen
);
170 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen
, alloclen
, 1);
172 driver
= git__calloc(1, alloclen
);
173 GIT_ERROR_CHECK_ALLOC(driver
);
175 memcpy(driver
->name
, name
, namelen
);
180 *namelen_out
= namelen
;
185 static int git_diff_driver_builtin(
186 git_diff_driver
**out
,
187 git_diff_driver_registry
*reg
,
188 const char *driver_name
)
190 git_diff_driver_definition
*ddef
= NULL
;
191 git_diff_driver
*drv
= NULL
;
195 for (idx
= 0; idx
< ARRAY_SIZE(builtin_defs
); ++idx
) {
196 if (!strcasecmp(driver_name
, builtin_defs
[idx
].name
)) {
197 ddef
= &builtin_defs
[idx
];
204 if ((error
= diff_driver_alloc(&drv
, NULL
, ddef
->name
)) < 0)
207 drv
->type
= DIFF_DRIVER_PATTERNLIST
;
210 (error
= diff_driver_add_patterns(
211 drv
, ddef
->fns
, ddef
->flags
)) < 0)
215 (error
= git_regexp_compile(&drv
->word_pattern
, ddef
->words
, ddef
->flags
)) < 0)
218 if ((error
= git_strmap_set(reg
->drivers
, drv
->name
, drv
)) < 0)
223 git_diff_driver_free(drv
);
230 static int git_diff_driver_load(
231 git_diff_driver
**out
, git_repository
*repo
, const char *driver_name
)
234 git_diff_driver_registry
*reg
;
235 git_diff_driver
*drv
;
237 git_config
*cfg
= NULL
;
238 git_str name
= GIT_STR_INIT
;
239 git_config_entry
*ce
= NULL
;
240 bool found_driver
= false;
242 if ((reg
= git_repository_driver_registry(repo
)) == NULL
)
245 if ((drv
= git_strmap_get(reg
->drivers
, driver_name
)) != NULL
) {
250 if ((error
= diff_driver_alloc(&drv
, &namelen
, driver_name
)) < 0)
253 drv
->type
= DIFF_DRIVER_AUTO
;
255 /* if you can't read config for repo, just use default driver */
256 if (git_repository_config_snapshot(&cfg
, repo
) < 0) {
261 if ((error
= git_str_printf(&name
, "diff.%s.binary", driver_name
)) < 0)
264 switch (git_config__get_bool_force(cfg
, name
.ptr
, -1)) {
266 /* if diff.<driver>.binary is true, just return the binary driver */
267 *out
= &diff_driver_binary
;
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
;
276 /* diff.<driver>.binary unspecified or "auto", so just continue */
280 /* TODO: warn if diff.<name>.command or diff.<name>.textconv are set */
282 git_str_truncate(&name
, namelen
+ strlen("diff.."));
283 if ((error
= git_str_PUTS(&name
, "xfuncname")) < 0)
286 if ((error
= git_config_get_multivar_foreach(
287 cfg
, name
.ptr
, NULL
, diff_driver_xfuncname
, drv
)) < 0) {
288 if (error
!= GIT_ENOTFOUND
)
290 git_error_clear(); /* no diff.<driver>.xfuncname, so just continue */
293 git_str_truncate(&name
, namelen
+ strlen("diff.."));
294 if ((error
= git_str_PUTS(&name
, "funcname")) < 0)
297 if ((error
= git_config_get_multivar_foreach(
298 cfg
, name
.ptr
, NULL
, diff_driver_funcname
, drv
)) < 0) {
299 if (error
!= GIT_ENOTFOUND
)
301 git_error_clear(); /* no diff.<driver>.funcname, so just continue */
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
;
310 git_str_truncate(&name
, namelen
+ strlen("diff.."));
311 if ((error
= git_str_PUTS(&name
, "wordregex")) < 0)
314 if ((error
= git_config__lookup_entry(&ce
, cfg
, name
.ptr
, false)) < 0)
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)))
321 /* TODO: warn about bad regex instead of failure */
325 /* TODO: look up diff.<driver>.algorithm to turn on minimal / patience
326 * diff in drv->other_flags
329 /* if no driver config found at all, fall back on AUTO driver */
333 /* store driver in registry */
334 if ((error
= git_strmap_set(reg
->drivers
, drv
->name
, drv
)) < 0)
340 git_config_entry_free(ce
);
341 git_str_dispose(&name
);
342 git_config_free(cfg
);
345 int error2
= git_diff_driver_builtin(out
, reg
, driver_name
);
350 if (drv
&& drv
!= *out
)
351 git_diff_driver_free(drv
);
356 int git_diff_driver_lookup(
357 git_diff_driver
**out
, git_repository
*repo
,
358 git_attr_session
*attrsession
, const char *path
)
361 const char *values
[1], *attrs
[] = { "diff" };
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 */;
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
;
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
) {
388 *out
= &diff_driver_auto
;
393 void git_diff_driver_free(git_diff_driver
*driver
)
395 git_diff_driver_pattern
*pat
;
400 while ((pat
= git_array_pop(driver
->fn_patterns
)) != NULL
)
401 git_regexp_dispose(&pat
->re
);
402 git_array_clear(driver
->fn_patterns
);
404 git_regexp_dispose(&driver
->word_pattern
);
409 void git_diff_driver_update_options(
410 uint32_t *option_flags
, git_diff_driver
*driver
)
412 if ((*option_flags
& FORCE_DIFFABLE
) == 0)
413 *option_flags
|= driver
->binary_flags
;
415 *option_flags
|= driver
->other_flags
;
418 int git_diff_driver_content_is_binary(
419 git_diff_driver
*driver
, const char *content
, size_t content_len
)
421 git_str search
= GIT_STR_INIT
;
425 git_str_attach_notowned(&search
, content
,
426 min(content_len
, GIT_FILTER_BYTES_TO_CHECK_NUL
));
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.
433 /* previously was: if (git_str_is_binary(&search)) */
434 if (git_str_contains_nul(&search
))
440 static int diff_context_line__simple(
441 git_diff_driver
*driver
, git_str
*line
)
443 char firstch
= line
->ptr
[0];
445 return (git__isalpha(firstch
) || firstch
== '_' || firstch
== '$');
448 static int diff_context_line__pattern_match(
449 git_diff_driver
*driver
, git_str
*line
)
451 size_t i
, maxi
= git_array_size(driver
->fn_patterns
);
452 git_regmatch pmatch
[2];
454 for (i
= 0; i
< maxi
; ++i
) {
455 git_diff_driver_pattern
*pat
= git_array_get(driver
->fn_patterns
, i
);
457 if (!git_regexp_search(&pat
->re
, line
->ptr
, 2, pmatch
)) {
458 if (pat
->flags
& REG_NEGATE
)
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
);
474 static long diff_context_find(
481 git_diff_find_context_payload
*ctxt
= payload
;
483 if (git_str_set(&ctxt
->line
, line
, (size_t)line_len
) < 0)
485 git_str_rtrim(&ctxt
->line
);
487 if (!ctxt
->line
.size
)
490 if (!ctxt
->match_line
|| !ctxt
->match_line(ctxt
->driver
, &ctxt
->line
))
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
);
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
)
505 *findfn_out
= driver
? diff_context_find
: NULL
;
507 memset(payload_out
, 0, sizeof(*payload_out
));
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);
516 void git_diff_find_context_clear(git_diff_find_context_payload
*payload
)
519 git_str_dispose(&payload
->line
);
520 payload
->driver
= NULL
;