#include "color.h"
#include "attr.h"
#include "run-command.h"
+#include "utf8.h"
#ifdef NO_FAST_WORKING_DIRECTORY
#define FAST_WORKING_DIRECTORY 0
static int diff_detect_rename_default;
static int diff_rename_limit_default = 100;
static int diff_use_color_default;
+static const char *external_diff_cmd_cfg;
int diff_auto_refresh_index = 1;
static char diff_colors[][COLOR_MAXLEN] = {
static struct ll_diff_driver {
const char *name;
struct ll_diff_driver *next;
- char *cmd;
+ const char *cmd;
} *user_diff, **user_diff_tail;
-static void read_config_if_needed(void)
-{
- if (!user_diff_tail) {
- user_diff_tail = &user_diff;
- git_config(git_diff_ui_config);
- }
-}
-
/*
* Currently there is only "diff.<drivername>.command" variable;
* because there are "diff.color.<slot>" variables, we are parsing
user_diff_tail = &(drv->next);
}
- if (!value)
- return error("%s: lacks value", var);
- drv->cmd = strdup(value);
- return 0;
+ return git_config_string(&(drv->cmd), var, value);
}
/*
return 0;
}
if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
- diff_use_color_default = git_config_colorbool(var, value);
+ diff_use_color_default = git_config_colorbool(var, value, -1);
return 0;
}
if (!strcmp(var, "diff.renames")) {
diff_auto_refresh_index = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "diff.external")) {
+ if (!value)
+ return config_error_nonbool(var);
+ external_diff_cmd_cfg = xstrdup(value);
+ return 0;
+ }
if (!prefixcmp(var, "diff.")) {
const char *ep = strrchr(var, '.');
- if (ep != var + 4) {
- if (!strcmp(ep, ".command"))
- return parse_lldiff_command(var, ep, value);
- if (!strcmp(ep, ".funcname"))
- return parse_funcname_pattern(var, ep, value);
- }
+ if (ep != var + 4 && !strcmp(ep, ".command"))
+ return parse_lldiff_command(var, ep, value);
}
+
+ return git_diff_basic_config(var, value);
+}
+
+int git_diff_basic_config(const char *var, const char *value)
+{
if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
int slot = parse_diff_color_slot(var, 11);
+ if (!value)
+ return config_error_nonbool(var);
color_parse(value, var, diff_colors[slot]);
return 0;
}
+ if (!prefixcmp(var, "diff.")) {
+ const char *ep = strrchr(var, '.');
+ if (ep != var + 4) {
+ if (!strcmp(ep, ".funcname")) {
+ if (!value)
+ return config_error_nonbool(var);
+ return parse_funcname_pattern(var, ep, value);
+ }
+ }
+ }
+
return git_default_config(var, value);
}
if (done_preparing)
return external_diff_cmd;
external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
+ if (!external_diff_cmd)
+ external_diff_cmd = external_diff_cmd_cfg;
done_preparing = 1;
return external_diff_cmd;
}
}
}
-static void copy_file(int prefix, const char *data, int size,
- const char *set, const char *reset)
+static void copy_file_with_prefix(int prefix, const char *data, int size,
+ const char *set, const char *reset)
{
int ch, nl_just_seen = 1;
while (0 < size--) {
const char *name_b,
struct diff_filespec *one,
struct diff_filespec *two,
- int color_diff)
+ struct diff_options *o)
{
int lc_a, lc_b;
+ int color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
const char *name_a_tab, *name_b_tab;
const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
const char *old = diff_get_color(color_diff, DIFF_FILE_OLD);
const char *new = diff_get_color(color_diff, DIFF_FILE_NEW);
const char *reset = diff_get_color(color_diff, DIFF_RESET);
+ static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
name_a += (*name_a == '/');
name_b += (*name_b == '/');
name_a_tab = strchr(name_a, ' ') ? "\t" : "";
name_b_tab = strchr(name_b, ' ') ? "\t" : "";
+ strbuf_reset(&a_name);
+ strbuf_reset(&b_name);
+ quote_two_c_style(&a_name, o->a_prefix, name_a, 0);
+ quote_two_c_style(&b_name, o->b_prefix, name_b, 0);
+
diff_populate_filespec(one, 0);
diff_populate_filespec(two, 0);
lc_a = count_lines(one->data, one->size);
lc_b = count_lines(two->data, two->size);
- printf("%s--- a/%s%s%s\n%s+++ b/%s%s%s\n%s@@ -",
- metainfo, name_a, name_a_tab, reset,
- metainfo, name_b, name_b_tab, reset, fraginfo);
+ printf("%s--- %s%s%s\n%s+++ %s%s%s\n%s@@ -",
+ metainfo, a_name.buf, name_a_tab, reset,
+ metainfo, b_name.buf, name_b_tab, reset, fraginfo);
print_line_count(lc_a);
printf(" +");
print_line_count(lc_b);
printf(" @@%s\n", reset);
if (lc_a)
- copy_file('-', one->data, one->size, old, reset);
+ copy_file_with_prefix('-', one->data, one->size, old, reset);
if (lc_b)
- copy_file('+', two->data, two->size, new, reset);
+ copy_file_with_prefix('+', two->data, two->size, new, reset);
}
static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
ecb.outf = xdiff_outf;
ecb.priv = diff_words;
diff_words->xm.consume = fn_out_diff_words_aux;
- xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+ xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
free(minus.ptr);
free(plus.ptr);
}
}
+typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
+
struct emit_callback {
struct xdiff_emit_state xm;
int nparents, color_diff;
+ unsigned ws_rule;
+ sane_truncate_fn truncate;
const char **label_path;
struct diff_words_data *diff_words;
int *found_changesp;
static void emit_line(const char *set, const char *reset, const char *line, int len)
{
- if (len > 0 && line[len-1] == '\n')
- len--;
fputs(set, stdout);
fwrite(line, len, 1, stdout);
- puts(reset);
-}
-
-static void emit_line_with_ws(int nparents,
- const char *set, const char *reset, const char *ws,
- const char *line, int len)
-{
- int col0 = nparents;
- int last_tab_in_indent = -1;
- int last_space_in_indent = -1;
- int i;
- int tail = len;
- int need_highlight_leading_space = 0;
- /* The line is a newly added line. Does it have funny leading
- * whitespaces? In indent, SP should never precede a TAB.
- */
- for (i = col0; i < len; i++) {
- if (line[i] == '\t') {
- last_tab_in_indent = i;
- if (0 <= last_space_in_indent)
- need_highlight_leading_space = 1;
- }
- else if (line[i] == ' ')
- last_space_in_indent = i;
- else
- break;
- }
- fputs(set, stdout);
- fwrite(line, col0, 1, stdout);
fputs(reset, stdout);
- if (((i == len) || line[i] == '\n') && i != col0) {
- /* The whole line was indent */
- emit_line(ws, reset, line + col0, len - col0);
- return;
- }
- i = col0;
- if (need_highlight_leading_space) {
- while (i < last_tab_in_indent) {
- if (line[i] == ' ') {
- fputs(ws, stdout);
- putchar(' ');
- fputs(reset, stdout);
- }
- else
- putchar(line[i]);
- i++;
- }
- }
- tail = len - 1;
- if (line[tail] == '\n' && i < tail)
- tail--;
- while (i < tail) {
- if (!isspace(line[tail]))
- break;
- tail--;
- }
- if ((i < tail && line[tail + 1] != '\n')) {
- /* This has whitespace between tail+1..len */
- fputs(set, stdout);
- fwrite(line + i, tail - i + 1, 1, stdout);
- fputs(reset, stdout);
- emit_line(ws, reset, line + tail + 1, len - tail - 1);
- }
- else
- emit_line(set, reset, line + i, len - i);
}
static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
if (!*ws)
emit_line(set, reset, line, len);
- else
- emit_line_with_ws(ecbdata->nparents, set, reset, ws,
- line, len);
+ else {
+ /* Emit just the prefix, then the rest. */
+ emit_line(set, reset, line, ecbdata->nparents);
+ (void)check_and_emit_line(line + ecbdata->nparents,
+ len - ecbdata->nparents, ecbdata->ws_rule,
+ stdout, set, reset, ws);
+ }
+}
+
+static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
+{
+ const char *cp;
+ unsigned long allot;
+ size_t l = len;
+
+ if (ecb->truncate)
+ return ecb->truncate(line, len);
+ cp = line;
+ allot = l;
+ while (0 < l) {
+ (void) utf8_width(&cp, &l);
+ if (!cp)
+ break; /* truncated in the middle? */
+ }
+ return allot - l;
}
static void fn_out_consume(void *priv, char *line, unsigned long len)
int i;
int color;
struct emit_callback *ecbdata = priv;
- const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
+ const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
+ const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
*(ecbdata->found_changesp) = 1;
name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : "";
printf("%s--- %s%s%s\n",
- set, ecbdata->label_path[0], reset, name_a_tab);
+ meta, ecbdata->label_path[0], reset, name_a_tab);
printf("%s+++ %s%s%s\n",
- set, ecbdata->label_path[1], reset, name_b_tab);
+ meta, ecbdata->label_path[1], reset, name_b_tab);
ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
}
;
if (2 <= i && i < len && line[i] == ' ') {
ecbdata->nparents = i - 1;
+ len = sane_truncate_line(ecbdata, line, len);
emit_line(diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
reset, line, len);
+ if (line[len-1] != '\n')
+ putchar('\n');
return;
}
if (len < ecbdata->nparents) {
- set = reset;
emit_line(reset, reset, line, len);
return;
}
diff_words_show(ecbdata->diff_words);
line++;
len--;
- emit_line(set, reset, line, len);
+ emit_line(plain, reset, line, len);
return;
}
for (i = 0; i < ecbdata->nparents && len; i++) {
int nr;
int alloc;
struct diffstat_file {
+ char *from_name;
char *name;
+ char *print_name;
unsigned is_unmerged:1;
unsigned is_binary:1;
unsigned is_renamed:1;
}
diffstat->files[diffstat->nr++] = x;
if (name_b) {
- x->name = pprint_rename(name_a, name_b);
+ x->from_name = xstrdup(name_a);
+ x->name = xstrdup(name_b);
x->is_renamed = 1;
}
- else
+ else {
+ x->from_name = NULL;
x->name = xstrdup(name_a);
+ }
return x;
}
printf("%s", reset);
}
+static void fill_print_name(struct diffstat_file *file)
+{
+ char *pname;
+
+ if (file->print_name)
+ return;
+
+ if (!file->is_renamed) {
+ struct strbuf buf;
+ strbuf_init(&buf, 0);
+ if (quote_c_style(file->name, &buf, NULL, 0)) {
+ pname = strbuf_detach(&buf, NULL);
+ } else {
+ pname = file->name;
+ strbuf_release(&buf);
+ }
+ } else {
+ pname = pprint_rename(file->from_name, file->name);
+ }
+ file->print_name = pname;
+}
+
static void show_stats(struct diffstat_t* data, struct diff_options *options)
{
int i, len, add, del, total, adds = 0, dels = 0;
for (i = 0; i < data->nr; i++) {
struct diffstat_file *file = data->files[i];
int change = file->added + file->deleted;
-
- if (!file->is_renamed) { /* renames are already quoted by pprint_rename */
- struct strbuf buf;
- strbuf_init(&buf, 0);
- if (quote_c_style(file->name, &buf, NULL, 0)) {
- free(file->name);
- file->name = strbuf_detach(&buf, NULL);
- } else {
- strbuf_release(&buf);
- }
- }
-
- len = strlen(file->name);
+ fill_print_name(file);
+ len = strlen(file->print_name);
if (max_len < len)
max_len = len;
for (i = 0; i < data->nr; i++) {
const char *prefix = "";
- char *name = data->files[i]->name;
+ char *name = data->files[i]->print_name;
int added = data->files[i]->added;
int deleted = data->files[i]->deleted;
int name_len;
printf("%s%d%s", add_c, added, reset);
printf(" bytes");
printf("\n");
- goto free_diffstat_file;
+ continue;
}
else if (data->files[i]->is_unmerged) {
show_name(prefix, name, len, reset, set);
printf(" Unmerged\n");
- goto free_diffstat_file;
+ continue;
}
else if (!data->files[i]->is_renamed &&
(added + deleted == 0)) {
total_files--;
- goto free_diffstat_file;
+ continue;
}
/*
show_graph('+', add, add_c, reset);
show_graph('-', del, del_c, reset);
putchar('\n');
- free_diffstat_file:
- free(data->files[i]->name);
- free(data->files[i]);
}
- free(data->files);
printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
set, total_files, adds, dels, reset);
}
dels += deleted;
}
}
- free(data->files[i]->name);
- free(data->files[i]);
}
- free(data->files);
-
printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
total_files, adds, dels);
}
{
int i;
+ if (data->nr == 0)
+ return;
+
for (i = 0; i < data->nr; i++) {
struct diffstat_file *file = data->files[i];
printf("-\t-\t");
else
printf("%d\t%d\t", file->added, file->deleted);
- if (!file->is_renamed) {
- write_name_quoted(file->name, stdout, options->line_termination);
+ if (options->line_termination) {
+ fill_print_name(file);
+ if (!file->is_renamed)
+ write_name_quoted(file->name, stdout,
+ options->line_termination);
+ else {
+ fputs(file->print_name, stdout);
+ putchar(options->line_termination);
+ }
} else {
- fputs(file->name, stdout);
- putchar(options->line_termination);
+ if (file->is_renamed) {
+ putchar('\0');
+ write_name_quoted(file->from_name, stdout, '\0');
+ }
+ write_name_quoted(file->name, stdout, '\0');
}
}
}
+static void free_diffstat_info(struct diffstat_t *diffstat)
+{
+ int i;
+ for (i = 0; i < diffstat->nr; i++) {
+ struct diffstat_file *f = diffstat->files[i];
+ if (f->name != f->print_name)
+ free(f->print_name);
+ free(f->name);
+ free(f->from_name);
+ free(f);
+ }
+ free(diffstat->files);
+}
+
struct checkdiff_t {
struct xdiff_emit_state xm;
const char *filename;
int lineno, color_diff;
+ unsigned ws_rule;
+ unsigned status;
};
static void checkdiff_consume(void *priv, char *line, unsigned long len)
const char *ws = diff_get_color(data->color_diff, DIFF_WHITESPACE);
const char *reset = diff_get_color(data->color_diff, DIFF_RESET);
const char *set = diff_get_color(data->color_diff, DIFF_FILE_NEW);
+ char *err;
if (line[0] == '+') {
- int i, spaces = 0, space_before_tab = 0, white_space_at_end = 0;
-
- /* check space before tab */
- for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++)
- if (line[i] == ' ')
- spaces++;
- if (line[i - 1] == '\t' && spaces)
- space_before_tab = 1;
-
- /* check white space at line end */
- if (line[len - 1] == '\n')
- len--;
- if (isspace(line[len - 1]))
- white_space_at_end = 1;
-
- if (space_before_tab || white_space_at_end) {
- printf("%s:%d: %s", data->filename, data->lineno, ws);
- if (space_before_tab) {
- printf("space before tab");
- if (white_space_at_end)
- putchar(',');
- }
- if (white_space_at_end)
- printf("white space at end");
- printf(":%s ", reset);
- emit_line_with_ws(1, set, reset, ws, line, len);
- }
-
data->lineno++;
+ data->status = check_and_emit_line(line + 1, len - 1,
+ data->ws_rule, NULL, NULL, NULL, NULL);
+ if (!data->status)
+ return;
+ err = whitespace_error_string(data->status);
+ printf("%s:%d: %s.\n", data->filename, data->lineno, err);
+ free(err);
+ emit_line(set, reset, line, 1);
+ (void)check_and_emit_line(line + 1, len - 1, data->ws_rule,
+ stdout, set, reset, ws);
} else if (line[0] == ' ')
data->lineno++;
else if (line[0] == '@') {
char *plus = strchr(line, '+');
if (plus)
- data->lineno = strtol(plus, NULL, 10);
+ data->lineno = strtol(plus, NULL, 10) - 1;
else
die("invalid diff");
}
{
struct funcname_pattern *pp;
- read_config_if_needed();
for (pp = funcname_pattern_list; pp; pp = pp->next)
if (!strcmp(ident, pp->name))
return pp->pattern;
/*
* And define built-in fallback patterns here. Note that
- * these can be overriden by the user's config settings.
+ * these can be overridden by the user's config settings.
*/
for (i = 0; i < ARRAY_SIZE(builtin_funcname_pattern); i++)
if (!strcmp(ident, builtin_funcname_pattern[i].name))
const char *set = diff_get_color_opt(o, DIFF_METAINFO);
const char *reset = diff_get_color_opt(o, DIFF_RESET);
- a_one = quote_two("a/", name_a + (*name_a == '/'));
- b_two = quote_two("b/", name_b + (*name_b == '/'));
+ a_one = quote_two(o->a_prefix, name_a + (*name_a == '/'));
+ b_two = quote_two(o->b_prefix, name_b + (*name_b == '/'));
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
printf("%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
if ((one->mode ^ two->mode) & S_IFMT)
goto free_ab_and_return;
if (complete_rewrite) {
- emit_rewrite_diff(name_a, name_b, one, two,
- DIFF_OPT_TST(o, COLOR_DIFF));
+ emit_rewrite_diff(name_a, name_b, one, two, o);
o->found_changes = 1;
goto free_ab_and_return;
}
ecbdata.label_path = lbl;
ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
ecbdata.found_changesp = &o->found_changes;
+ ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
xecfg.ctxlen = o->context;
xecfg.flags = XDL_EMIT_FUNCNAMES;
if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
ecbdata.diff_words =
xcalloc(1, sizeof(struct diff_words_data));
- xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
free_diff_words_data(&ecbdata);
}
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
ecb.outf = xdiff_outf;
ecb.priv = diffstat;
- xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
}
free_and_return:
data.filename = name_b ? name_b : name_a;
data.lineno = 0;
data.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
+ data.ws_rule = whitespace_rule(data.filename);
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
die("unable to read files to diff");
xpp.flags = XDF_NEED_MINIMAL;
ecb.outf = xdiff_outf;
ecb.priv = &data;
- xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
}
free_and_return:
diff_free_filespec_data(one);
diff_free_filespec_data(two);
+ if (data.status)
+ DIFF_OPT_SET(o, CHECK_FAILED);
}
struct diff_filespec *alloc_filespec(const char *path)
!ATTR_UNSET(value)) {
struct ll_diff_driver *drv;
- read_config_if_needed();
for (drv = user_diff; drv; drv = drv->next)
if (!strcmp(drv->name, value))
return drv->cmd;
else
DIFF_OPT_CLR(options, COLOR_DIFF);
options->detect_rename = diff_detect_rename_default;
+
+ options->a_prefix = "a/";
+ options->b_prefix = "b/";
}
int diff_setup_done(struct diff_options *options)
else if (40 < options->abbrev)
options->abbrev = 40;
}
+ else if (!prefixcmp(arg, "--src-prefix="))
+ options->a_prefix = arg + 13;
+ else if (!prefixcmp(arg, "--dst-prefix="))
+ options->b_prefix = arg + 13;
+ else if (!strcmp(arg, "--no-prefix"))
+ options->a_prefix = options->b_prefix = "";
else
return 0;
return 1;
xecfg.flags = XDL_EMIT_FUNCNAMES;
ecb.outf = xdiff_outf;
ecb.priv = &data;
- xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
}
SHA1_Final(sha1, &ctx);
show_numstat(&diffstat, options);
if (output_format & DIFF_FORMAT_DIFFSTAT)
show_stats(&diffstat, options);
- else if (output_format & DIFF_FORMAT_SHORTSTAT)
+ if (output_format & DIFF_FORMAT_SHORTSTAT)
show_shortstats(&diffstat);
+ free_diffstat_info(&diffstat);
separator++;
}
DIFF_OPT_CLR(options, HAS_CHANGES);
}
+int diff_result_code(struct diff_options *opt, int status)
+{
+ int result = 0;
+ if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
+ !(opt->output_format & DIFF_FORMAT_CHECKDIFF))
+ return status;
+ if (DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
+ DIFF_OPT_TST(opt, HAS_CHANGES))
+ result |= 01;
+ if ((opt->output_format & DIFF_FORMAT_CHECKDIFF) &&
+ DIFF_OPT_TST(opt, CHECK_FAILED))
+ result |= 02;
+ return result;
+}
void diff_addremove(struct diff_options *options,
int addremove, unsigned mode,