mirror of
				https://git.proxmox.com/git/libgit2
				synced 2025-11-04 12:12:46 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			504 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			504 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (C) the libgit2 contributors. All rights reserved.
 | 
						|
 *
 | 
						|
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 | 
						|
 * a Linking Exception. For full terms see the included COPYING file.
 | 
						|
 */
 | 
						|
#include "common.h"
 | 
						|
 | 
						|
#include "git2/attr.h"
 | 
						|
 | 
						|
#include "diff.h"
 | 
						|
#include "diff_patch.h"
 | 
						|
#include "diff_driver.h"
 | 
						|
#include "strmap.h"
 | 
						|
#include "map.h"
 | 
						|
#include "buf_text.h"
 | 
						|
#include "config.h"
 | 
						|
#include "repository.h"
 | 
						|
 | 
						|
GIT__USE_STRMAP;
 | 
						|
 | 
						|
typedef enum {
 | 
						|
	DIFF_DRIVER_AUTO = 0,
 | 
						|
	DIFF_DRIVER_BINARY = 1,
 | 
						|
	DIFF_DRIVER_TEXT = 2,
 | 
						|
	DIFF_DRIVER_PATTERNLIST = 3,
 | 
						|
} git_diff_driver_t;
 | 
						|
 | 
						|
typedef struct {
 | 
						|
	regex_t re;
 | 
						|
	int flags;
 | 
						|
} git_diff_driver_pattern;
 | 
						|
 | 
						|
enum {
 | 
						|
	REG_NEGATE = (1 << 15) /* get out of the way of existing flags */
 | 
						|
};
 | 
						|
 | 
						|
/* data for finding function context for a given file type */
 | 
						|
struct git_diff_driver {
 | 
						|
	git_diff_driver_t type;
 | 
						|
	uint32_t binary_flags;
 | 
						|
	uint32_t other_flags;
 | 
						|
	git_array_t(git_diff_driver_pattern) fn_patterns;
 | 
						|
	regex_t  word_pattern;
 | 
						|
	char name[GIT_FLEX_ARRAY];
 | 
						|
};
 | 
						|
 | 
						|
#include "userdiff.h"
 | 
						|
 | 
						|
struct git_diff_driver_registry {
 | 
						|
	git_strmap *drivers;
 | 
						|
};
 | 
						|
 | 
						|
#define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY)
 | 
						|
 | 
						|
static git_diff_driver global_drivers[3] = {
 | 
						|
	{ DIFF_DRIVER_AUTO,   0, 0, },
 | 
						|
	{ DIFF_DRIVER_BINARY, GIT_DIFF_FORCE_BINARY, 0 },
 | 
						|
	{ DIFF_DRIVER_TEXT,   GIT_DIFF_FORCE_TEXT, 0 },
 | 
						|
};
 | 
						|
 | 
						|
git_diff_driver_registry *git_diff_driver_registry_new()
 | 
						|
{
 | 
						|
	git_diff_driver_registry *reg =
 | 
						|
		git__calloc(1, sizeof(git_diff_driver_registry));
 | 
						|
	if (!reg)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	if (git_strmap_alloc(®->drivers) < 0) {
 | 
						|
		git_diff_driver_registry_free(reg);
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	return reg;
 | 
						|
}
 | 
						|
 | 
						|
void git_diff_driver_registry_free(git_diff_driver_registry *reg)
 | 
						|
{
 | 
						|
	git_diff_driver *drv;
 | 
						|
 | 
						|
	if (!reg)
 | 
						|
		return;
 | 
						|
 | 
						|
	git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv));
 | 
						|
	git_strmap_free(reg->drivers);
 | 
						|
	git__free(reg);
 | 
						|
}
 | 
						|
 | 
						|
static int diff_driver_add_patterns(
 | 
						|
	git_diff_driver *drv, const char *regex_str, int regex_flags)
 | 
						|
{
 | 
						|
	int error = 0;
 | 
						|
	const char *scan, *end;
 | 
						|
	git_diff_driver_pattern *pat = NULL;
 | 
						|
	git_buf buf = GIT_BUF_INIT;
 | 
						|
 | 
						|
	for (scan = regex_str; scan; scan = end) {
 | 
						|
		/* get pattern to fill in */
 | 
						|
		if ((pat = git_array_alloc(drv->fn_patterns)) == NULL) {
 | 
						|
			error = -1;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		pat->flags = regex_flags;
 | 
						|
		if (*scan == '!') {
 | 
						|
			pat->flags |= REG_NEGATE;
 | 
						|
			++scan;
 | 
						|
		}
 | 
						|
 | 
						|
		if ((end = strchr(scan, '\n')) != NULL) {
 | 
						|
			error = git_buf_set(&buf, scan, end - scan);
 | 
						|
			end++;
 | 
						|
		} else {
 | 
						|
			error = git_buf_sets(&buf, scan);
 | 
						|
		}
 | 
						|
		if (error < 0)
 | 
						|
			break;
 | 
						|
 | 
						|
		if ((error = regcomp(&pat->re, buf.ptr, regex_flags)) < 0) {
 | 
						|
			/* if regex fails to compile, warn? fail? */
 | 
						|
			error = giterr_set_regex(&pat->re, error);
 | 
						|
			regfree(&pat->re);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (error && pat != NULL)
 | 
						|
		(void)git_array_pop(drv->fn_patterns); /* release last item */
 | 
						|
	git_buf_free(&buf);
 | 
						|
 | 
						|
	return error;
 | 
						|
}
 | 
						|
 | 
						|
static int diff_driver_xfuncname(const git_config_entry *entry, void *payload)
 | 
						|
{
 | 
						|
	return diff_driver_add_patterns(payload, entry->value, REG_EXTENDED);
 | 
						|
}
 | 
						|
 | 
						|
static int diff_driver_funcname(const git_config_entry *entry, void *payload)
 | 
						|
{
 | 
						|
	return diff_driver_add_patterns(payload, entry->value, 0);
 | 
						|
}
 | 
						|
 | 
						|
static git_diff_driver_registry *git_repository_driver_registry(
 | 
						|
	git_repository *repo)
 | 
						|
{
 | 
						|
	if (!repo->diff_drivers) {
 | 
						|
		git_diff_driver_registry *reg = git_diff_driver_registry_new();
 | 
						|
		reg = git__compare_and_swap(&repo->diff_drivers, NULL, reg);
 | 
						|
 | 
						|
		if (reg != NULL) /* if we race, free losing allocation */
 | 
						|
			git_diff_driver_registry_free(reg);
 | 
						|
	}
 | 
						|
 | 
						|
	if (!repo->diff_drivers)
 | 
						|
		giterr_set(GITERR_REPOSITORY, "Unable to create diff driver registry");
 | 
						|
 | 
						|
	return repo->diff_drivers;
 | 
						|
}
 | 
						|
 | 
						|
static int git_diff_driver_builtin(
 | 
						|
	git_diff_driver **out,
 | 
						|
	git_diff_driver_registry *reg,
 | 
						|
	const char *driver_name)
 | 
						|
{
 | 
						|
	int error = 0;
 | 
						|
	git_diff_driver_definition *ddef = NULL;
 | 
						|
	git_diff_driver *drv = NULL;
 | 
						|
	size_t namelen, idx;
 | 
						|
 | 
						|
	for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) {
 | 
						|
		if (!strcasecmp(driver_name, builtin_defs[idx].name)) {
 | 
						|
			ddef = &builtin_defs[idx];
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (!ddef)
 | 
						|
		goto done;
 | 
						|
 | 
						|
	namelen = strlen(ddef->name);
 | 
						|
 | 
						|
	drv = git__calloc(1, sizeof(git_diff_driver) + namelen + 1);
 | 
						|
	GITERR_CHECK_ALLOC(drv);
 | 
						|
 | 
						|
	drv->type = DIFF_DRIVER_PATTERNLIST;
 | 
						|
	memcpy(drv->name, ddef->name, namelen);
 | 
						|
 | 
						|
	if (ddef->fns &&
 | 
						|
		(error = diff_driver_add_patterns(
 | 
						|
			drv, ddef->fns, ddef->flags | REG_EXTENDED)) < 0)
 | 
						|
		goto done;
 | 
						|
 | 
						|
	if (ddef->words &&
 | 
						|
		(error = regcomp(
 | 
						|
			&drv->word_pattern, ddef->words, ddef->flags | REG_EXTENDED)))
 | 
						|
	{
 | 
						|
		error = giterr_set_regex(&drv->word_pattern, error);
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	git_strmap_insert(reg->drivers, drv->name, drv, error);
 | 
						|
	if (error > 0)
 | 
						|
		error = 0;
 | 
						|
 | 
						|
done:
 | 
						|
	if (error && drv)
 | 
						|
		git_diff_driver_free(drv);
 | 
						|
	else
 | 
						|
		*out = drv;
 | 
						|
 | 
						|
	return error;
 | 
						|
}
 | 
						|
 | 
						|
static int git_diff_driver_load(
 | 
						|
	git_diff_driver **out, git_repository *repo, const char *driver_name)
 | 
						|
{
 | 
						|
	int error = 0;
 | 
						|
	git_diff_driver_registry *reg;
 | 
						|
	git_diff_driver *drv = NULL;
 | 
						|
	size_t namelen = strlen(driver_name);
 | 
						|
	khiter_t pos;
 | 
						|
	git_config *cfg;
 | 
						|
	git_buf name = GIT_BUF_INIT;
 | 
						|
	const git_config_entry *ce;
 | 
						|
	bool found_driver = false;
 | 
						|
 | 
						|
	if ((reg = git_repository_driver_registry(repo)) == NULL)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	pos = git_strmap_lookup_index(reg->drivers, driver_name);
 | 
						|
	if (git_strmap_valid_index(reg->drivers, pos)) {
 | 
						|
		*out = git_strmap_value_at(reg->drivers, pos);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	drv = git__calloc(1, sizeof(git_diff_driver) + namelen + 1);
 | 
						|
	GITERR_CHECK_ALLOC(drv);
 | 
						|
	drv->type = DIFF_DRIVER_AUTO;
 | 
						|
	memcpy(drv->name, driver_name, namelen);
 | 
						|
 | 
						|
	/* if you can't read config for repo, just use default driver */
 | 
						|
	if (git_repository_config_snapshot(&cfg, repo) < 0) {
 | 
						|
		giterr_clear();
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	if ((error = git_buf_printf(&name, "diff.%s.binary", driver_name)) < 0)
 | 
						|
		goto done;
 | 
						|
 | 
						|
	switch (git_config__get_bool_force(cfg, name.ptr, -1)) {
 | 
						|
	case true:
 | 
						|
		/* if diff.<driver>.binary is true, just return the binary driver */
 | 
						|
		*out = &global_drivers[DIFF_DRIVER_BINARY];
 | 
						|
		goto done;
 | 
						|
	case false:
 | 
						|
		/* if diff.<driver>.binary is false, force binary checks off */
 | 
						|
		/* but still may have custom function context patterns, etc. */
 | 
						|
		drv->binary_flags = GIT_DIFF_FORCE_TEXT;
 | 
						|
		found_driver = true;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		/* diff.<driver>.binary unspecified or "auto", so just continue */
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	/* TODO: warn if diff.<name>.command or diff.<name>.textconv are set */
 | 
						|
 | 
						|
	git_buf_truncate(&name, namelen + strlen("diff.."));
 | 
						|
	git_buf_put(&name, "xfuncname", strlen("xfuncname"));
 | 
						|
	if ((error = git_config_get_multivar_foreach(
 | 
						|
			cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) {
 | 
						|
		if (error != GIT_ENOTFOUND)
 | 
						|
			goto done;
 | 
						|
		giterr_clear(); /* no diff.<driver>.xfuncname, so just continue */
 | 
						|
	}
 | 
						|
 | 
						|
	git_buf_truncate(&name, namelen + strlen("diff.."));
 | 
						|
	git_buf_put(&name, "funcname", strlen("funcname"));
 | 
						|
	if ((error = git_config_get_multivar_foreach(
 | 
						|
			cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) {
 | 
						|
		if (error != GIT_ENOTFOUND)
 | 
						|
			goto done;
 | 
						|
		giterr_clear(); /* no diff.<driver>.funcname, so just continue */
 | 
						|
	}
 | 
						|
 | 
						|
	/* if we found any patterns, set driver type to use correct callback */
 | 
						|
	if (git_array_size(drv->fn_patterns) > 0) {
 | 
						|
		drv->type = DIFF_DRIVER_PATTERNLIST;
 | 
						|
		found_driver = true;
 | 
						|
	}
 | 
						|
 | 
						|
	git_buf_truncate(&name, namelen + strlen("diff.."));
 | 
						|
	git_buf_put(&name, "wordregex", strlen("wordregex"));
 | 
						|
	if ((error = git_config__lookup_entry(&ce, cfg, name.ptr, false)) < 0)
 | 
						|
		goto done;
 | 
						|
	if (!ce || !ce->value)
 | 
						|
		/* no diff.<driver>.wordregex, so just continue */;
 | 
						|
	else if (!(error = regcomp(&drv->word_pattern, ce->value, REG_EXTENDED)))
 | 
						|
		found_driver = true;
 | 
						|
	else {
 | 
						|
		/* TODO: warn about bad regex instead of failure */
 | 
						|
		error = giterr_set_regex(&drv->word_pattern, error);
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	/* TODO: look up diff.<driver>.algorithm to turn on minimal / patience
 | 
						|
	 * diff in drv->other_flags
 | 
						|
	 */
 | 
						|
 | 
						|
	/* if no driver config found at all, fall back on AUTO driver */
 | 
						|
	if (!found_driver)
 | 
						|
		goto done;
 | 
						|
 | 
						|
	/* store driver in registry */
 | 
						|
	git_strmap_insert(reg->drivers, drv->name, drv, error);
 | 
						|
	if (error < 0)
 | 
						|
		goto done;
 | 
						|
	error = 0;
 | 
						|
 | 
						|
	*out = drv;
 | 
						|
 | 
						|
done:
 | 
						|
	git_buf_free(&name);
 | 
						|
	git_config_free(cfg);
 | 
						|
 | 
						|
	if (!*out) {
 | 
						|
		int error2 = git_diff_driver_builtin(out, reg, driver_name);
 | 
						|
		if (!error)
 | 
						|
			error = error2;
 | 
						|
	}
 | 
						|
 | 
						|
	if (drv && drv != *out)
 | 
						|
		git_diff_driver_free(drv);
 | 
						|
 | 
						|
	return error;
 | 
						|
}
 | 
						|
 | 
						|
int git_diff_driver_lookup(
 | 
						|
	git_diff_driver **out, git_repository *repo, const char *path)
 | 
						|
{
 | 
						|
	int error = 0;
 | 
						|
	const char *value;
 | 
						|
 | 
						|
	assert(out);
 | 
						|
	*out = NULL;
 | 
						|
 | 
						|
	if (!repo || !path || !strlen(path))
 | 
						|
		/* just use the auto value */;
 | 
						|
	else if ((error = git_attr_get(&value, repo, 0, path, "diff")) < 0)
 | 
						|
		/* return error below */;
 | 
						|
	else if (GIT_ATTR_UNSPECIFIED(value))
 | 
						|
		/* just use the auto value */;
 | 
						|
	else if (GIT_ATTR_FALSE(value))
 | 
						|
		*out = &global_drivers[DIFF_DRIVER_BINARY];
 | 
						|
	else if (GIT_ATTR_TRUE(value))
 | 
						|
		*out = &global_drivers[DIFF_DRIVER_TEXT];
 | 
						|
 | 
						|
	/* otherwise look for driver information in config and build driver */
 | 
						|
	else if ((error = git_diff_driver_load(out, repo, value)) < 0) {
 | 
						|
		if (error == GIT_ENOTFOUND) {
 | 
						|
			error = 0;
 | 
						|
			giterr_clear();
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!*out)
 | 
						|
		*out = &global_drivers[DIFF_DRIVER_AUTO];
 | 
						|
 | 
						|
	return error;
 | 
						|
}
 | 
						|
 | 
						|
void git_diff_driver_free(git_diff_driver *driver)
 | 
						|
{
 | 
						|
	size_t i;
 | 
						|
 | 
						|
	if (!driver)
 | 
						|
		return;
 | 
						|
 | 
						|
	for (i = 0; i < git_array_size(driver->fn_patterns); ++i)
 | 
						|
		regfree(& git_array_get(driver->fn_patterns, i)->re);
 | 
						|
	git_array_clear(driver->fn_patterns);
 | 
						|
 | 
						|
	regfree(&driver->word_pattern);
 | 
						|
 | 
						|
	git__free(driver);
 | 
						|
}
 | 
						|
 | 
						|
void git_diff_driver_update_options(
 | 
						|
	uint32_t *option_flags, git_diff_driver *driver)
 | 
						|
{
 | 
						|
	if ((*option_flags & FORCE_DIFFABLE) == 0)
 | 
						|
		*option_flags |= driver->binary_flags;
 | 
						|
 | 
						|
	*option_flags |= driver->other_flags;
 | 
						|
}
 | 
						|
 | 
						|
int git_diff_driver_content_is_binary(
 | 
						|
	git_diff_driver *driver, const char *content, size_t content_len)
 | 
						|
{
 | 
						|
	git_buf search;
 | 
						|
 | 
						|
	search.ptr   = (char *)content;
 | 
						|
	search.size  = min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL);
 | 
						|
	search.asize = 0;
 | 
						|
 | 
						|
	GIT_UNUSED(driver);
 | 
						|
 | 
						|
	/* TODO: provide encoding / binary detection callbacks that can
 | 
						|
	 * be UTF-8 aware, etc.  For now, instead of trying to be smart,
 | 
						|
	 * let's just use the simple NUL-byte detection that core git uses.
 | 
						|
	 */
 | 
						|
 | 
						|
	/* previously was: if (git_buf_text_is_binary(&search)) */
 | 
						|
	if (git_buf_text_contains_nul(&search))
 | 
						|
		return 1;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int diff_context_line__simple(
 | 
						|
	git_diff_driver *driver, git_buf *line)
 | 
						|
{
 | 
						|
	char firstch = line->ptr[0];
 | 
						|
	GIT_UNUSED(driver);
 | 
						|
	return (git__isalpha(firstch) || firstch == '_' || firstch == '$');
 | 
						|
}
 | 
						|
 | 
						|
static int diff_context_line__pattern_match(
 | 
						|
	git_diff_driver *driver, git_buf *line)
 | 
						|
{
 | 
						|
	size_t i, maxi = git_array_size(driver->fn_patterns);
 | 
						|
	regmatch_t pmatch[2];
 | 
						|
 | 
						|
	for (i = 0; i < maxi; ++i) {
 | 
						|
		git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i);
 | 
						|
 | 
						|
		if (!regexec(&pat->re, line->ptr, 2, pmatch, 0)) {
 | 
						|
			if (pat->flags & REG_NEGATE)
 | 
						|
				return false;
 | 
						|
 | 
						|
			/* use pmatch data to trim line data */
 | 
						|
			i = (pmatch[1].rm_so >= 0) ? 1 : 0;
 | 
						|
			git_buf_consume(line, git_buf_cstr(line) + pmatch[i].rm_so);
 | 
						|
			git_buf_truncate(line, pmatch[i].rm_eo - pmatch[i].rm_so);
 | 
						|
			git_buf_rtrim(line);
 | 
						|
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
static long diff_context_find(
 | 
						|
	const char *line,
 | 
						|
	long line_len,
 | 
						|
	char *out,
 | 
						|
	long out_size,
 | 
						|
	void *payload)
 | 
						|
{
 | 
						|
	git_diff_find_context_payload *ctxt = payload;
 | 
						|
 | 
						|
	if (git_buf_set(&ctxt->line, line, (size_t)line_len) < 0)
 | 
						|
		return -1;
 | 
						|
	git_buf_rtrim(&ctxt->line);
 | 
						|
 | 
						|
	if (!ctxt->line.size)
 | 
						|
		return -1;
 | 
						|
 | 
						|
	if (!ctxt->match_line || !ctxt->match_line(ctxt->driver, &ctxt->line))
 | 
						|
		return -1;
 | 
						|
 | 
						|
	if (out_size > (long)ctxt->line.size)
 | 
						|
		out_size = (long)ctxt->line.size;
 | 
						|
	memcpy(out, ctxt->line.ptr, (size_t)out_size);
 | 
						|
 | 
						|
	return out_size;
 | 
						|
}
 | 
						|
 | 
						|
void git_diff_find_context_init(
 | 
						|
	git_diff_find_context_fn *findfn_out,
 | 
						|
	git_diff_find_context_payload *payload_out,
 | 
						|
	git_diff_driver *driver)
 | 
						|
{
 | 
						|
	*findfn_out = driver ? diff_context_find : NULL;
 | 
						|
 | 
						|
	memset(payload_out, 0, sizeof(*payload_out));
 | 
						|
	if (driver) {
 | 
						|
		payload_out->driver = driver;
 | 
						|
		payload_out->match_line = (driver->type == DIFF_DRIVER_PATTERNLIST) ?
 | 
						|
			diff_context_line__pattern_match : diff_context_line__simple;
 | 
						|
		git_buf_init(&payload_out->line, 0);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void git_diff_find_context_clear(git_diff_find_context_payload *payload)
 | 
						|
{
 | 
						|
	if (payload) {
 | 
						|
		git_buf_free(&payload->line);
 | 
						|
		payload->driver = NULL;
 | 
						|
	}
 | 
						|
}
 | 
						|
 |