#include "diffcore.h"
#include "delta.h"
#include "xdiff-interface.h"
+#include "color.h"
static int use_size_cache;
static int diff_rename_limit_default = -1;
static int diff_use_color_default;
-/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
-static char diff_colors[][24] = {
+static char diff_colors[][COLOR_MAXLEN] = {
"\033[m", /* reset */
- "", /* normal */
- "\033[1m", /* bold */
- "\033[36m", /* cyan */
- "\033[31m", /* red */
- "\033[32m", /* green */
- "\033[33m" /* yellow */
+ "", /* PLAIN (normal) */
+ "\033[1m", /* METAINFO (bold) */
+ "\033[36m", /* FRAGINFO (cyan) */
+ "\033[31m", /* OLD (red) */
+ "\033[32m", /* NEW (green) */
+ "\033[33m", /* COMMIT (yellow) */
+ "\033[41m", /* WHITESPACE (red background) */
};
static int parse_diff_color_slot(const char *var, int ofs)
return DIFF_FILE_NEW;
if (!strcasecmp(var+ofs, "commit"))
return DIFF_COMMIT;
+ if (!strcasecmp(var+ofs, "whitespace"))
+ return DIFF_WHITESPACE;
die("bad config variable '%s'", var);
}
-static int parse_color(const char *name, int len)
-{
- static const char * const color_names[] = {
- "normal", "black", "red", "green", "yellow",
- "blue", "magenta", "cyan", "white"
- };
- char *end;
- int i;
- for (i = 0; i < ARRAY_SIZE(color_names); i++) {
- const char *str = color_names[i];
- if (!strncasecmp(name, str, len) && !str[len])
- return i - 1;
- }
- i = strtol(name, &end, 10);
- if (*name && !*end && i >= -1 && i <= 255)
- return i;
- return -2;
-}
-
-static int parse_attr(const char *name, int len)
-{
- static const int attr_values[] = { 1, 2, 4, 5, 7 };
- static const char * const attr_names[] = {
- "bold", "dim", "ul", "blink", "reverse"
- };
- int i;
- for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
- const char *str = attr_names[i];
- if (!strncasecmp(name, str, len) && !str[len])
- return attr_values[i];
- }
- return -1;
-}
-
-static void parse_diff_color_value(const char *value, const char *var, char *dst)
-{
- const char *ptr = value;
- int attr = -1;
- int fg = -2;
- int bg = -2;
-
- if (!strcasecmp(value, "reset")) {
- strcpy(dst, "\033[m");
- return;
- }
-
- /* [fg [bg]] [attr] */
- while (*ptr) {
- const char *word = ptr;
- int val, len = 0;
-
- while (word[len] && !isspace(word[len]))
- len++;
-
- ptr = word + len;
- while (*ptr && isspace(*ptr))
- ptr++;
-
- val = parse_color(word, len);
- if (val >= -1) {
- if (fg == -2) {
- fg = val;
- continue;
- }
- if (bg == -2) {
- bg = val;
- continue;
- }
- goto bad;
- }
- val = parse_attr(word, len);
- if (val < 0 || attr != -1)
- goto bad;
- attr = val;
- }
-
- if (attr >= 0 || fg >= 0 || bg >= 0) {
- int sep = 0;
-
- *dst++ = '\033';
- *dst++ = '[';
- if (attr >= 0) {
- *dst++ = '0' + attr;
- sep++;
- }
- if (fg >= 0) {
- if (sep++)
- *dst++ = ';';
- if (fg < 8) {
- *dst++ = '3';
- *dst++ = '0' + fg;
- } else {
- dst += sprintf(dst, "38;5;%d", fg);
- }
- }
- if (bg >= 0) {
- if (sep++)
- *dst++ = ';';
- if (bg < 8) {
- *dst++ = '4';
- *dst++ = '0' + bg;
- } else {
- dst += sprintf(dst, "48;5;%d", bg);
- }
- }
- *dst++ = 'm';
- }
- *dst = 0;
- return;
-bad:
- die("bad config value '%s' for variable '%s'", value, var);
-}
-
/*
* These are to give UI layer defaults.
* The core-level commands such as git-diff-files should
return 0;
}
if (!strcmp(var, "diff.color")) {
- if (!value)
- diff_use_color_default = 1; /* bool */
- else if (!strcasecmp(value, "auto")) {
- diff_use_color_default = 0;
- if (isatty(1) || (pager_in_use && pager_use_color)) {
- char *term = getenv("TERM");
- if (term && strcmp(term, "dumb"))
- diff_use_color_default = 1;
- }
- }
- else if (!strcasecmp(value, "never"))
- diff_use_color_default = 0;
- else if (!strcasecmp(value, "always"))
- diff_use_color_default = 1;
- else
- diff_use_color_default = git_config_bool(var, value);
+ diff_use_color_default = git_config_colorbool(var, value);
return 0;
}
if (!strcmp(var, "diff.renames")) {
}
if (!strncmp(var, "diff.color.", 11)) {
int slot = parse_diff_color_slot(var, 11);
- parse_diff_color_value(value, var, diff_colors[slot]);
+ color_parse(value, var, diff_colors[slot]);
return 0;
}
return git_default_config(var, value);
return NULL;
needlen = quote_c_style(str, NULL, NULL, 0);
if (!needlen)
- return strdup(str);
+ return xstrdup(str);
xp = xmalloc(needlen + 1);
quote_c_style(str, xp, NULL, 0);
return xp;
diff_populate_filespec(two, 0);
lc_a = count_lines(one->data, one->size);
lc_b = count_lines(two->data, two->size);
- printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
+ printf("--- a/%s\n+++ b/%s\n@@ -", name_a, name_b);
print_line_count(lc_a);
printf(" +");
print_line_count(lc_b);
return "";
}
+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_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
+{
+ int col0 = ecbdata->nparents;
+ int last_tab_in_indent = -1;
+ int last_space_in_indent = -1;
+ int i;
+ int tail = len;
+ int need_highlight_leading_space = 0;
+ const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
+ const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
+
+ if (!*ws) {
+ emit_line(set, reset, line, len);
+ return;
+ }
+
+ /* 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 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 *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
;
if (2 <= i && i < len && line[i] == ' ') {
ecbdata->nparents = i - 1;
- set = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+ emit_line(diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
+ reset, line, len);
+ return;
}
- else if (len < ecbdata->nparents)
+
+ if (len < ecbdata->nparents) {
set = reset;
- else {
- int nparents = ecbdata->nparents;
- int color = DIFF_PLAIN;
- if (ecbdata->diff_words && nparents != 1)
- /* fall back to normal diff */
- free_diff_words_data(ecbdata);
- if (ecbdata->diff_words) {
- if (line[0] == '-') {
- diff_words_append(line, len,
- &ecbdata->diff_words->minus);
- return;
- } else if (line[0] == '+') {
- diff_words_append(line, len,
- &ecbdata->diff_words->plus);
- return;
- }
- if (ecbdata->diff_words->minus.text.size ||
- ecbdata->diff_words->plus.text.size)
- diff_words_show(ecbdata->diff_words);
- line++;
- len--;
- } else
- for (i = 0; i < nparents && len; i++) {
- if (line[i] == '-')
- color = DIFF_FILE_OLD;
- else if (line[i] == '+')
- color = DIFF_FILE_NEW;
- }
- set = diff_get_color(ecbdata->color_diff, color);
+ emit_line(reset, reset, line, len);
+ return;
}
- if (len > 0 && line[len-1] == '\n')
+
+ color = DIFF_PLAIN;
+ if (ecbdata->diff_words && ecbdata->nparents != 1)
+ /* fall back to normal diff */
+ free_diff_words_data(ecbdata);
+ if (ecbdata->diff_words) {
+ if (line[0] == '-') {
+ diff_words_append(line, len,
+ &ecbdata->diff_words->minus);
+ return;
+ } else if (line[0] == '+') {
+ diff_words_append(line, len,
+ &ecbdata->diff_words->plus);
+ return;
+ }
+ if (ecbdata->diff_words->minus.text.size ||
+ ecbdata->diff_words->plus.text.size)
+ diff_words_show(ecbdata->diff_words);
+ line++;
len--;
- fputs (set, stdout);
- fwrite (line, len, 1, stdout);
- puts (reset);
+ emit_line(set, reset, line, len);
+ return;
+ }
+ for (i = 0; i < ecbdata->nparents && len; i++) {
+ if (line[i] == '-')
+ color = DIFF_FILE_OLD;
+ else if (line[i] == '+')
+ color = DIFF_FILE_NEW;
+ }
+
+ if (color != DIFF_FILE_NEW) {
+ emit_line(diff_get_color(ecbdata->color_diff, color),
+ reset, line, len);
+ return;
+ }
+ emit_add_line(reset, ecbdata, line, len);
}
static char *pprint_rename(const char *a, const char *b)
x->is_renamed = 1;
}
else
- x->name = strdup(name_a);
+ x->name = xstrdup(name_a);
return x;
}
x->deleted++;
}
-static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]= "----------------------------------------------------------------------";
const char mime_boundary_leader[] = "------------";
-static void show_stats(struct diffstat_t* data)
+static int scale_linear(int it, int width, int max_change)
+{
+ /*
+ * make sure that at least one '-' is printed if there were deletions,
+ * and likewise for '+'.
+ */
+ if (max_change < 2)
+ return it;
+ return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+}
+
+static void show_name(const char *prefix, const char *name, int len,
+ const char *reset, const char *set)
+{
+ printf(" %s%s%-*s%s |", set, prefix, len, name, reset);
+}
+
+static void show_graph(char ch, int cnt, const char *set, const char *reset)
+{
+ if (cnt <= 0)
+ return;
+ printf("%s", set);
+ while (cnt--)
+ putchar(ch);
+ printf("%s", reset);
+}
+
+static void show_stats(struct diffstat_t* data, struct diff_options *options)
{
int i, len, add, del, total, adds = 0, dels = 0;
- int max, max_change = 0, max_len = 0;
+ int max_change = 0, max_len = 0;
int total_files = data->nr;
+ int width, name_width;
+ const char *reset, *set, *add_c, *del_c;
if (data->nr == 0)
return;
+ width = options->stat_width ? options->stat_width : 80;
+ name_width = options->stat_name_width ? options->stat_name_width : 50;
+
+ /* Sanity: give at least 5 columns to the graph,
+ * but leave at least 10 columns for the name.
+ */
+ if (width < name_width + 15) {
+ if (name_width <= 25)
+ width = name_width + 15;
+ else
+ name_width = width - 15;
+ }
+
+ /* Find the longest filename and max number of changes */
+ reset = diff_get_color(options->color_diff, DIFF_RESET);
+ set = diff_get_color(options->color_diff, DIFF_PLAIN);
+ add_c = diff_get_color(options->color_diff, DIFF_FILE_NEW);
+ del_c = diff_get_color(options->color_diff, DIFF_FILE_OLD);
+
for (i = 0; i < data->nr; i++) {
struct diffstat_file *file = data->files[i];
+ int change = file->added + file->deleted;
+
+ len = quote_c_style(file->name, NULL, NULL, 0);
+ if (len) {
+ char *qname = xmalloc(len + 1);
+ quote_c_style(file->name, qname, NULL, 0);
+ free(file->name);
+ file->name = qname;
+ }
len = strlen(file->name);
if (max_len < len)
if (file->is_binary || file->is_unmerged)
continue;
- if (max_change < file->added + file->deleted)
- max_change = file->added + file->deleted;
+ if (max_change < change)
+ max_change = change;
}
+ /* Compute the width of the graph part;
+ * 10 is for one blank at the beginning of the line plus
+ * " | count " between the name and the graph.
+ *
+ * From here on, name_width is the width of the name area,
+ * and width is the width of the graph area.
+ */
+ name_width = (name_width < max_len) ? name_width : max_len;
+ if (width < (name_width + 10) + max_change)
+ width = width - (name_width + 10);
+ else
+ width = max_change;
+
for (i = 0; i < data->nr; i++) {
const char *prefix = "";
char *name = data->files[i]->name;
int added = data->files[i]->added;
int deleted = data->files[i]->deleted;
-
- if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
- char *qname = xmalloc(len + 1);
- quote_c_style(name, qname, NULL, 0);
- free(name);
- data->files[i]->name = name = qname;
- }
+ int name_len;
/*
* "scale" the filename
*/
- len = strlen(name);
- max = max_len;
- if (max > 50)
- max = 50;
- if (len > max) {
+ len = name_width;
+ name_len = strlen(name);
+ if (name_width < name_len) {
char *slash;
prefix = "...";
- max -= 3;
- name += len - max;
+ len -= 3;
+ name += name_len - len;
slash = strchr(name, '/');
if (slash)
name = slash;
}
- len = max;
-
- /*
- * scale the add/delete
- */
- max = max_change;
- if (max + len > 70)
- max = 70 - len;
if (data->files[i]->is_binary) {
- printf(" %s%-*s | Bin\n", prefix, len, name);
+ show_name(prefix, name, len, reset, set);
+ printf(" Bin\n");
goto free_diffstat_file;
}
else if (data->files[i]->is_unmerged) {
- printf(" %s%-*s | Unmerged\n", prefix, len, name);
+ show_name(prefix, name, len, reset, set);
+ printf(" Unmerged\n");
goto free_diffstat_file;
}
else if (!data->files[i]->is_renamed &&
goto free_diffstat_file;
}
+ /*
+ * scale the add/delete
+ */
add = added;
del = deleted;
total = add + del;
adds += add;
dels += del;
- if (max_change > 0) {
- total = (total * max + max_change / 2) / max_change;
- add = (add * max + max_change / 2) / max_change;
- del = total - add;
+ if (width <= max_change) {
+ add = scale_linear(add, width, max_change);
+ del = scale_linear(del, width, max_change);
+ total = add + del;
}
- printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
- len, name, added + deleted,
- add, pluses, del, minuses);
+ show_name(prefix, name, len, reset, set);
+ printf("%5d ", added + deleted);
+ 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(" %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
+ printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
+ set, total_files, adds, dels, reset);
}
struct checkdiff_t {
return deflated;
}
-static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
+static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two)
{
void *cp;
void *delta;
unsigned long deflate_size;
unsigned long data_size;
- printf("GIT binary patch\n");
/* We could do deflated delta, or we could do just deflated two,
* whichever is smaller.
*/
free(data);
}
+static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
+{
+ printf("GIT binary patch\n");
+ emit_binary_diff_body(one, two);
+ emit_binary_diff_body(two, one);
+}
+
#define FIRST_FEW_BYTES 8000
static int mmfile_is_binary(mmfile_t *mf)
{
if (hashcmp(one->sha1, two->sha1)) {
int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
+ if (o->binary) {
+ mmfile_t mf;
+ if ((!fill_mmfile(&mf, one) && mmfile_is_binary(&mf)) ||
+ (!fill_mmfile(&mf, two) && mmfile_is_binary(&mf)))
+ abbrev = 40;
+ }
len += snprintf(msg + len, sizeof(msg) - len,
"index %.*s..%.*s",
abbrev, sha1_to_hex(one->sha1),
else if (!strcmp(arg, "--patch-with-raw")) {
options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
}
- else if (!strcmp(arg, "--stat"))
+ else if (!strncmp(arg, "--stat", 6)) {
+ char *end;
+ int width = options->stat_width;
+ int name_width = options->stat_name_width;
+ arg += 6;
+ end = (char *)arg;
+
+ switch (*arg) {
+ case '-':
+ if (!strncmp(arg, "-width=", 7))
+ width = strtoul(arg + 7, &end, 10);
+ else if (!strncmp(arg, "-name-width=", 12))
+ name_width = strtoul(arg + 12, &end, 10);
+ break;
+ case '=':
+ width = strtoul(arg+1, &end, 10);
+ if (*end == ',')
+ name_width = strtoul(end+1, &end, 10);
+ }
+
+ /* Important! This checks all the error cases! */
+ if (*end)
+ return 0;
options->output_format |= DIFF_FORMAT_DIFFSTAT;
+ options->stat_name_width = name_width;
+ options->stat_width = width;
+ }
else if (!strcmp(arg, "--check"))
options->output_format |= DIFF_FORMAT_CHECKDIFF;
else if (!strcmp(arg, "--summary"))
options->full_index = 1;
else if (!strcmp(arg, "--binary")) {
options->output_format |= DIFF_FORMAT_PATCH;
- options->full_index = options->binary = 1;
+ options->binary = 1;
}
else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) {
options->text = 1;
if (check_pair_status(p))
diff_flush_stat(p, options, &diffstat);
}
- show_stats(&diffstat);
+ show_stats(&diffstat, options);
separator++;
}
}
}
+ if (output_format & DIFF_FORMAT_CALLBACK)
+ options->format_callback(q, options, options->format_callback_data);
+
for (i = 0; i < q->nr; i++)
diff_free_filepair(q->queue[i]);
free_queue: