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.
12 #include "diff_patch.h"
13 #include "diff_driver.h"
18 #include "repository.h"
24 DIFF_DRIVER_BINARY
= 1,
26 DIFF_DRIVER_PATTERNLIST
= 3,
32 } git_diff_driver_pattern
;
35 REG_NEGATE
= (1 << 15) /* get out of the way of existing flags */
38 /* data for finding function context for a given file type */
39 struct git_diff_driver
{
40 git_diff_driver_t type
;
41 uint32_t binary_flags
;
43 git_array_t(git_diff_driver_pattern
) fn_patterns
;
45 char name
[GIT_FLEX_ARRAY
];
50 struct git_diff_driver_registry
{
54 #define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY)
56 static git_diff_driver global_drivers
[3] = {
57 { DIFF_DRIVER_AUTO
, 0, 0, },
58 { DIFF_DRIVER_BINARY
, GIT_DIFF_FORCE_BINARY
, 0 },
59 { DIFF_DRIVER_TEXT
, GIT_DIFF_FORCE_TEXT
, 0 },
62 git_diff_driver_registry
*git_diff_driver_registry_new()
64 git_diff_driver_registry
*reg
=
65 git__calloc(1, sizeof(git_diff_driver_registry
));
69 if (git_strmap_alloc(®
->drivers
) < 0) {
70 git_diff_driver_registry_free(reg
);
77 void git_diff_driver_registry_free(git_diff_driver_registry
*reg
)
84 git_strmap_foreach_value(reg
->drivers
, drv
, git_diff_driver_free(drv
));
85 git_strmap_free(reg
->drivers
);
89 static int diff_driver_add_patterns(
90 git_diff_driver
*drv
, const char *regex_str
, int regex_flags
)
93 const char *scan
, *end
;
94 git_diff_driver_pattern
*pat
= NULL
;
95 git_buf buf
= GIT_BUF_INIT
;
97 for (scan
= regex_str
; scan
; scan
= end
) {
98 /* get pattern to fill in */
99 if ((pat
= git_array_alloc(drv
->fn_patterns
)) == NULL
) {
104 pat
->flags
= regex_flags
;
106 pat
->flags
|= REG_NEGATE
;
110 if ((end
= strchr(scan
, '\n')) != NULL
) {
111 error
= git_buf_set(&buf
, scan
, end
- scan
);
114 error
= git_buf_sets(&buf
, scan
);
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
);
127 if (error
&& pat
!= NULL
)
128 (void)git_array_pop(drv
->fn_patterns
); /* release last item */
134 static int diff_driver_xfuncname(const git_config_entry
*entry
, void *payload
)
136 return diff_driver_add_patterns(payload
, entry
->value
, REG_EXTENDED
);
139 static int diff_driver_funcname(const git_config_entry
*entry
, void *payload
)
141 return diff_driver_add_patterns(payload
, entry
->value
, 0);
144 static git_diff_driver_registry
*git_repository_driver_registry(
145 git_repository
*repo
)
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
);
151 if (reg
!= NULL
) /* if we race, free losing allocation */
152 git_diff_driver_registry_free(reg
);
155 if (!repo
->diff_drivers
)
156 giterr_set(GITERR_REPOSITORY
, "Unable to create diff driver registry");
158 return repo
->diff_drivers
;
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 GITERR_CHECK_ALLOC_ADD(&alloclen
, driverlen
, namelen
);
170 GITERR_CHECK_ALLOC_ADD(&alloclen
, alloclen
, 1);
172 driver
= git__calloc(1, alloclen
);
173 GITERR_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
)
191 git_diff_driver_definition
*ddef
= NULL
;
192 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
| REG_EXTENDED
)) < 0)
216 &drv
->word_pattern
, ddef
->words
, ddef
->flags
| REG_EXTENDED
)))
218 error
= giterr_set_regex(&drv
->word_pattern
, error
);
222 git_strmap_insert(reg
->drivers
, drv
->name
, drv
, error
);
228 git_diff_driver_free(drv
);
235 static int git_diff_driver_load(
236 git_diff_driver
**out
, git_repository
*repo
, const char *driver_name
)
239 git_diff_driver_registry
*reg
;
240 git_diff_driver
*drv
= NULL
;
243 git_config
*cfg
= NULL
;
244 git_buf name
= GIT_BUF_INIT
;
245 git_config_entry
*ce
= NULL
;
246 bool found_driver
= false;
248 if ((reg
= git_repository_driver_registry(repo
)) == NULL
)
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
);
257 if ((error
= diff_driver_alloc(&drv
, &namelen
, driver_name
)) < 0)
260 drv
->type
= DIFF_DRIVER_AUTO
;
262 /* if you can't read config for repo, just use default driver */
263 if (git_repository_config_snapshot(&cfg
, repo
) < 0) {
268 if ((error
= git_buf_printf(&name
, "diff.%s.binary", driver_name
)) < 0)
271 switch (git_config__get_bool_force(cfg
, name
.ptr
, -1)) {
273 /* if diff.<driver>.binary is true, just return the binary driver */
274 *out
= &global_drivers
[DIFF_DRIVER_BINARY
];
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
;
283 /* diff.<driver>.binary unspecified or "auto", so just continue */
287 /* TODO: warn if diff.<name>.command or diff.<name>.textconv are set */
289 git_buf_truncate(&name
, namelen
+ strlen("diff.."));
290 git_buf_put(&name
, "xfuncname", strlen("xfuncname"));
291 if ((error
= git_config_get_multivar_foreach(
292 cfg
, name
.ptr
, NULL
, diff_driver_xfuncname
, drv
)) < 0) {
293 if (error
!= GIT_ENOTFOUND
)
295 giterr_clear(); /* no diff.<driver>.xfuncname, so just continue */
298 git_buf_truncate(&name
, namelen
+ strlen("diff.."));
299 git_buf_put(&name
, "funcname", strlen("funcname"));
300 if ((error
= git_config_get_multivar_foreach(
301 cfg
, name
.ptr
, NULL
, diff_driver_funcname
, drv
)) < 0) {
302 if (error
!= GIT_ENOTFOUND
)
304 giterr_clear(); /* no diff.<driver>.funcname, so just continue */
307 /* if we found any patterns, set driver type to use correct callback */
308 if (git_array_size(drv
->fn_patterns
) > 0) {
309 drv
->type
= DIFF_DRIVER_PATTERNLIST
;
313 git_buf_truncate(&name
, namelen
+ strlen("diff.."));
314 git_buf_put(&name
, "wordregex", strlen("wordregex"));
315 if ((error
= git_config__lookup_entry(&ce
, cfg
, name
.ptr
, false)) < 0)
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
)))
322 /* TODO: warn about bad regex instead of failure */
323 error
= giterr_set_regex(&drv
->word_pattern
, error
);
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 git_strmap_insert(reg
->drivers
, drv
->name
, drv
, error
);
344 git_config_entry_free(ce
);
346 git_config_free(cfg
);
349 int error2
= git_diff_driver_builtin(out
, reg
, driver_name
);
354 if (drv
&& drv
!= *out
)
355 git_diff_driver_free(drv
);
360 int git_diff_driver_lookup(
361 git_diff_driver
**out
, git_repository
*repo
, const char *path
)
369 if (!repo
|| !path
|| !strlen(path
))
370 /* just use the auto value */;
371 else if ((error
= git_attr_get(&value
, repo
, 0, path
, "diff")) < 0)
372 /* return error below */;
373 else if (GIT_ATTR_UNSPECIFIED(value
))
374 /* just use the auto value */;
375 else if (GIT_ATTR_FALSE(value
))
376 *out
= &global_drivers
[DIFF_DRIVER_BINARY
];
377 else if (GIT_ATTR_TRUE(value
))
378 *out
= &global_drivers
[DIFF_DRIVER_TEXT
];
380 /* otherwise look for driver information in config and build driver */
381 else if ((error
= git_diff_driver_load(out
, repo
, value
)) < 0) {
382 if (error
== GIT_ENOTFOUND
) {
389 *out
= &global_drivers
[DIFF_DRIVER_AUTO
];
394 void git_diff_driver_free(git_diff_driver
*driver
)
401 for (i
= 0; i
< git_array_size(driver
->fn_patterns
); ++i
)
402 regfree(& git_array_get(driver
->fn_patterns
, i
)->re
);
403 git_array_clear(driver
->fn_patterns
);
405 regfree(&driver
->word_pattern
);
410 void git_diff_driver_update_options(
411 uint32_t *option_flags
, git_diff_driver
*driver
)
413 if ((*option_flags
& FORCE_DIFFABLE
) == 0)
414 *option_flags
|= driver
->binary_flags
;
416 *option_flags
|= driver
->other_flags
;
419 int git_diff_driver_content_is_binary(
420 git_diff_driver
*driver
, const char *content
, size_t content_len
)
422 git_buf search
= GIT_BUF_INIT
;
426 git_buf_attach_notowned(&search
, content
,
427 min(content_len
, GIT_FILTER_BYTES_TO_CHECK_NUL
));
429 /* TODO: provide encoding / binary detection callbacks that can
430 * be UTF-8 aware, etc. For now, instead of trying to be smart,
431 * let's just use the simple NUL-byte detection that core git uses.
434 /* previously was: if (git_buf_text_is_binary(&search)) */
435 if (git_buf_text_contains_nul(&search
))
441 static int diff_context_line__simple(
442 git_diff_driver
*driver
, git_buf
*line
)
444 char firstch
= line
->ptr
[0];
446 return (git__isalpha(firstch
) || firstch
== '_' || firstch
== '$');
449 static int diff_context_line__pattern_match(
450 git_diff_driver
*driver
, git_buf
*line
)
452 size_t i
, maxi
= git_array_size(driver
->fn_patterns
);
453 regmatch_t pmatch
[2];
455 for (i
= 0; i
< maxi
; ++i
) {
456 git_diff_driver_pattern
*pat
= git_array_get(driver
->fn_patterns
, i
);
458 if (!regexec(&pat
->re
, line
->ptr
, 2, pmatch
, 0)) {
459 if (pat
->flags
& REG_NEGATE
)
462 /* use pmatch data to trim line data */
463 i
= (pmatch
[1].rm_so
>= 0) ? 1 : 0;
464 git_buf_consume(line
, git_buf_cstr(line
) + pmatch
[i
].rm_so
);
465 git_buf_truncate(line
, pmatch
[i
].rm_eo
- pmatch
[i
].rm_so
);
475 static long diff_context_find(
482 git_diff_find_context_payload
*ctxt
= payload
;
484 if (git_buf_set(&ctxt
->line
, line
, (size_t)line_len
) < 0)
486 git_buf_rtrim(&ctxt
->line
);
488 if (!ctxt
->line
.size
)
491 if (!ctxt
->match_line
|| !ctxt
->match_line(ctxt
->driver
, &ctxt
->line
))
494 if (out_size
> (long)ctxt
->line
.size
)
495 out_size
= (long)ctxt
->line
.size
;
496 memcpy(out
, ctxt
->line
.ptr
, (size_t)out_size
);
501 void git_diff_find_context_init(
502 git_diff_find_context_fn
*findfn_out
,
503 git_diff_find_context_payload
*payload_out
,
504 git_diff_driver
*driver
)
506 *findfn_out
= driver
? diff_context_find
: NULL
;
508 memset(payload_out
, 0, sizeof(*payload_out
));
510 payload_out
->driver
= driver
;
511 payload_out
->match_line
= (driver
->type
== DIFF_DRIVER_PATTERNLIST
) ?
512 diff_context_line__pattern_match
: diff_context_line__simple
;
513 git_buf_init(&payload_out
->line
, 0);
517 void git_diff_find_context_clear(git_diff_find_context_payload
*payload
)
520 git_buf_free(&payload
->line
);
521 payload
->driver
= NULL
;