X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;ds=sidebyside;f=diff.c;h=8022e678d1fc26eab75d73d01110d635fba11fd7;hb=af05d67939f306a68c5f81ed30f65a82ae025661;hp=58fe7750f9ca7d63192164a3ac8da27553489d11;hpb=c0284cea3149cb20e24ee5e25b475e5f8edba84f;p=git.git diff --git a/diff.c b/diff.c index 58fe7750f..8022e678d 100644 --- a/diff.c +++ b/diff.c @@ -20,7 +20,7 @@ static int diff_detect_rename_default; static int diff_rename_limit_default = 100; -static int diff_use_color_default; +int diff_use_color_default = -1; static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; @@ -118,8 +118,7 @@ static int parse_funcname_pattern(const char *var, const char *ep, const char *v pp->next = funcname_pattern_list; funcname_pattern_list = pp; } - if (pp->pattern) - free(pp->pattern); + free(pp->pattern); pp->pattern = xstrdup(value); return 0; } @@ -191,7 +190,7 @@ int git_diff_basic_config(const char *var, const char *value) } } - return git_default_config(var, value); + return git_color_default_config(var, value); } static char *quote_two(const char *one, const char *two) @@ -257,40 +256,41 @@ static int count_lines(const char *data, int size) return count; } -static void print_line_count(int count) +static void print_line_count(FILE *file, int count) { switch (count) { case 0: - printf("0,0"); + fprintf(file, "0,0"); break; case 1: - printf("1"); + fprintf(file, "1"); break; default: - printf("1,%d", count); + fprintf(file, "1,%d", count); break; } } -static void copy_file(int prefix, const char *data, int size, - const char *set, const char *reset) +static void copy_file_with_prefix(FILE *file, + int prefix, const char *data, int size, + const char *set, const char *reset) { int ch, nl_just_seen = 1; while (0 < size--) { ch = *data++; if (nl_just_seen) { - fputs(set, stdout); - putchar(prefix); + fputs(set, file); + putc(prefix, file); } if (ch == '\n') { nl_just_seen = 1; - fputs(reset, stdout); + fputs(reset, file); } else nl_just_seen = 0; - putchar(ch); + putc(ch, file); } if (!nl_just_seen) - printf("%s\n\\ No newline at end of file\n", reset); + fprintf(file, "%s\n\\ No newline at end of file\n", reset); } static void emit_rewrite_diff(const char *name_a, @@ -323,17 +323,18 @@ static void emit_rewrite_diff(const char *name_a, diff_populate_filespec(two, 0); lc_a = count_lines(one->data, one->size); lc_b = count_lines(two->data, two->size); - 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); + fprintf(o->file, + "%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(o->file, lc_a); + fprintf(o->file, " +"); + print_line_count(o->file, lc_b); + fprintf(o->file, " @@%s\n", reset); if (lc_a) - copy_file('-', one->data, one->size, old, reset); + copy_file_with_prefix(o->file, '-', one->data, one->size, old, reset); if (lc_b) - copy_file('+', two->data, two->size, new, reset); + copy_file_with_prefix(o->file, '+', two->data, two->size, new, reset); } static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) @@ -373,9 +374,10 @@ static void diff_words_append(char *line, unsigned long len, struct diff_words_data { struct xdiff_emit_state xm; struct diff_words_buffer minus, plus; + FILE *file; }; -static void print_word(struct diff_words_buffer *buffer, int len, int color, +static void print_word(FILE *file, struct diff_words_buffer *buffer, int len, int color, int suppress_newline) { const char *ptr; @@ -392,15 +394,15 @@ static void print_word(struct diff_words_buffer *buffer, int len, int color, len--; } - fputs(diff_get_color(1, color), stdout); - fwrite(ptr, len, 1, stdout); - fputs(diff_get_color(1, DIFF_RESET), stdout); + fputs(diff_get_color(1, color), file); + fwrite(ptr, len, 1, file); + fputs(diff_get_color(1, DIFF_RESET), file); if (eol) { if (suppress_newline) buffer->suppressed_newline = 1; else - putchar('\n'); + putc('\n', file); } } @@ -410,20 +412,23 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) if (diff_words->minus.suppressed_newline) { if (line[0] != '+') - putchar('\n'); + putc('\n', diff_words->file); diff_words->minus.suppressed_newline = 0; } len--; switch (line[0]) { case '-': - print_word(&diff_words->minus, len, DIFF_FILE_OLD, 1); + print_word(diff_words->file, + &diff_words->minus, len, DIFF_FILE_OLD, 1); break; case '+': - print_word(&diff_words->plus, len, DIFF_FILE_NEW, 0); + print_word(diff_words->file, + &diff_words->plus, len, DIFF_FILE_NEW, 0); break; case ' ': - print_word(&diff_words->plus, len, DIFF_PLAIN, 0); + print_word(diff_words->file, + &diff_words->plus, len, DIFF_PLAIN, 0); diff_words->minus.current += len; break; } @@ -467,7 +472,7 @@ static void diff_words_show(struct diff_words_data *diff_words) diff_words->minus.text.size = diff_words->plus.text.size = 0; if (diff_words->minus.suppressed_newline) { - putchar('\n'); + putc('\n', diff_words->file); diff_words->minus.suppressed_newline = 0; } } @@ -482,6 +487,7 @@ struct emit_callback { const char **label_path; struct diff_words_data *diff_words; int *found_changesp; + FILE *file; }; static void free_diff_words_data(struct emit_callback *ecbdata) @@ -492,10 +498,8 @@ static void free_diff_words_data(struct emit_callback *ecbdata) ecbdata->diff_words->plus.text.size) diff_words_show(ecbdata->diff_words); - if (ecbdata->diff_words->minus.text.ptr) - free (ecbdata->diff_words->minus.text.ptr); - if (ecbdata->diff_words->plus.text.ptr) - free (ecbdata->diff_words->plus.text.ptr); + free (ecbdata->diff_words->minus.text.ptr); + free (ecbdata->diff_words->plus.text.ptr); free(ecbdata->diff_words); ecbdata->diff_words = NULL; } @@ -508,11 +512,11 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } -static void emit_line(const char *set, const char *reset, const char *line, int len) +static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) { - fputs(set, stdout); - fwrite(line, len, 1, stdout); - fputs(reset, stdout); + fputs(set, file); + fwrite(line, len, 1, file); + fputs(reset, file); } static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) @@ -521,13 +525,13 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); if (!*ws) - emit_line(set, reset, line, len); + emit_line(ecbdata->file, set, reset, line, len); else { /* Emit just the prefix, then the rest. */ - emit_line(set, reset, line, ecbdata->nparents); + emit_line(ecbdata->file, set, reset, line, ecbdata->nparents); (void)check_and_emit_line(line + ecbdata->nparents, len - ecbdata->nparents, ecbdata->ws_rule, - stdout, set, reset, ws); + ecbdata->file, set, reset, ws); } } @@ -566,10 +570,10 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : ""; name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : ""; - printf("%s--- %s%s%s\n", - meta, ecbdata->label_path[0], reset, name_a_tab); - printf("%s+++ %s%s%s\n", - meta, ecbdata->label_path[1], reset, name_b_tab); + fprintf(ecbdata->file, "%s--- %s%s%s\n", + meta, ecbdata->label_path[0], reset, name_a_tab); + fprintf(ecbdata->file, "%s+++ %s%s%s\n", + meta, ecbdata->label_path[1], reset, name_b_tab); ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } @@ -581,15 +585,16 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) 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), + emit_line(ecbdata->file, + diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), reset, line, len); if (line[len-1] != '\n') - putchar('\n'); + putc('\n', ecbdata->file); return; } if (len < ecbdata->nparents) { - emit_line(reset, reset, line, len); + emit_line(ecbdata->file, reset, reset, line, len); return; } @@ -612,7 +617,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) diff_words_show(ecbdata->diff_words); line++; len--; - emit_line(plain, reset, line, len); + emit_line(ecbdata->file, plain, reset, line, len); return; } for (i = 0; i < ecbdata->nparents && len; i++) { @@ -623,7 +628,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) } if (color != DIFF_FILE_NEW) { - emit_line(diff_get_color(ecbdata->color_diff, color), + emit_line(ecbdata->file, + diff_get_color(ecbdata->color_diff, color), reset, line, len); return; } @@ -762,20 +768,21 @@ static int scale_linear(int it, int width, int max_change) return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1); } -static void show_name(const char *prefix, const char *name, int len, +static void show_name(FILE *file, + const char *prefix, const char *name, int len, const char *reset, const char *set) { - printf(" %s%s%-*s%s |", set, prefix, len, name, reset); + fprintf(file, " %s%s%-*s%s |", set, prefix, len, name, reset); } -static void show_graph(char ch, int cnt, const char *set, const char *reset) +static void show_graph(FILE *file, char ch, int cnt, const char *set, const char *reset) { if (cnt <= 0) return; - printf("%s", set); + fprintf(file, "%s", set); while (cnt--) - putchar(ch); - printf("%s", reset); + putc(ch, file); + fprintf(file, "%s", reset); } static void fill_print_name(struct diffstat_file *file) @@ -880,18 +887,18 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) } if (data->files[i]->is_binary) { - show_name(prefix, name, len, reset, set); - printf(" Bin "); - printf("%s%d%s", del_c, deleted, reset); - printf(" -> "); - printf("%s%d%s", add_c, added, reset); - printf(" bytes"); - printf("\n"); + show_name(options->file, prefix, name, len, reset, set); + fprintf(options->file, " Bin "); + fprintf(options->file, "%s%d%s", del_c, deleted, reset); + fprintf(options->file, " -> "); + fprintf(options->file, "%s%d%s", add_c, added, reset); + fprintf(options->file, " bytes"); + fprintf(options->file, "\n"); continue; } else if (data->files[i]->is_unmerged) { - show_name(prefix, name, len, reset, set); - printf(" Unmerged\n"); + show_name(options->file, prefix, name, len, reset, set); + fprintf(options->file, " Unmerged\n"); continue; } else if (!data->files[i]->is_renamed && @@ -914,17 +921,18 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) del = scale_linear(del, width, max_change); total = add + del; } - 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'); - } - printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n", + show_name(options->file, prefix, name, len, reset, set); + fprintf(options->file, "%5d ", added + deleted); + show_graph(options->file, '+', add, add_c, reset); + show_graph(options->file, '-', del, del_c, reset); + fprintf(options->file, "\n"); + } + fprintf(options->file, + "%s %d files changed, %d insertions(+), %d deletions(-)%s\n", set, total_files, adds, dels, reset); } -static void show_shortstats(struct diffstat_t* data) +static void show_shortstats(struct diffstat_t* data, struct diff_options *options) { int i, adds = 0, dels = 0, total_files = data->nr; @@ -945,7 +953,7 @@ static void show_shortstats(struct diffstat_t* data) } } } - printf(" %d files changed, %d insertions(+), %d deletions(-)\n", + fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n", total_files, adds, dels); } @@ -960,26 +968,111 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) struct diffstat_file *file = data->files[i]; if (file->is_binary) - printf("-\t-\t"); + fprintf(options->file, "-\t-\t"); else - printf("%d\t%d\t", file->added, file->deleted); + fprintf(options->file, + "%d\t%d\t", file->added, file->deleted); if (options->line_termination) { fill_print_name(file); if (!file->is_renamed) - write_name_quoted(file->name, stdout, + write_name_quoted(file->name, options->file, options->line_termination); else { - fputs(file->print_name, stdout); - putchar(options->line_termination); + fputs(file->print_name, options->file); + putc(options->line_termination, options->file); } } else { if (file->is_renamed) { - putchar('\0'); - write_name_quoted(file->from_name, stdout, '\0'); + putc('\0', options->file); + write_name_quoted(file->from_name, options->file, '\0'); + } + write_name_quoted(file->name, options->file, '\0'); + } + } +} + +struct diffstat_dir { + struct diffstat_file **files; + int nr, percent, cumulative; +}; + +static long gather_dirstat(FILE *file, struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen) +{ + unsigned long this_dir = 0; + unsigned int sources = 0; + + while (dir->nr) { + struct diffstat_file *f = *dir->files; + int namelen = strlen(f->name); + unsigned long this; + char *slash; + + if (namelen < baselen) + break; + if (memcmp(f->name, base, baselen)) + break; + slash = strchr(f->name + baselen, '/'); + if (slash) { + int newbaselen = slash + 1 - f->name; + this = gather_dirstat(file, dir, changed, f->name, newbaselen); + sources++; + } else { + if (f->is_unmerged || f->is_binary) + this = 0; + else + this = f->added + f->deleted; + dir->files++; + dir->nr--; + sources += 2; + } + this_dir += this; + } + + /* + * We don't report dirstat's for + * - the top level + * - or cases where everything came from a single directory + * under this directory (sources == 1). + */ + if (baselen && sources != 1) { + int permille = this_dir * 1000 / changed; + if (permille) { + int percent = permille / 10; + if (percent >= dir->percent) { + fprintf(file, "%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base); + if (!dir->cumulative) + return 0; } - write_name_quoted(file->name, stdout, '\0'); } } + return this_dir; +} + +static void show_dirstat(struct diffstat_t *data, struct diff_options *options) +{ + int i; + unsigned long changed; + struct diffstat_dir dir; + + /* Calculate total changes */ + changed = 0; + for (i = 0; i < data->nr; i++) { + if (data->files[i]->is_binary || data->files[i]->is_unmerged) + continue; + changed += data->files[i]->added; + changed += data->files[i]->deleted; + } + + /* This can happen even with many files, if everything was renames */ + if (!changed) + return; + + /* Show all directories with more than x% of the changes */ + dir.files = data->files; + dir.nr = data->nr; + dir.percent = options->dirstat_percent; + dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE; + gather_dirstat(options->file, &dir, changed, "", 0); } static void free_diffstat_info(struct diffstat_t *diffstat) @@ -1002,6 +1095,7 @@ struct checkdiff_t { int lineno, color_diff; unsigned ws_rule; unsigned status; + FILE *file; }; static void checkdiff_consume(void *priv, char *line, unsigned long len) @@ -1019,11 +1113,11 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (!data->status) return; err = whitespace_error_string(data->status); - printf("%s:%d: %s.\n", data->filename, data->lineno, err); + fprintf(data->file, "%s:%d: %s.\n", data->filename, data->lineno, err); free(err); - emit_line(set, reset, line, 1); + emit_line(data->file, set, reset, line, 1); (void)check_and_emit_line(line + 1, len - 1, data->ws_rule, - stdout, set, reset, ws); + data->file, set, reset, ws); } else if (line[0] == ' ') data->lineno++; else if (line[0] == '@') { @@ -1059,7 +1153,7 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two) +static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two) { void *cp; void *delta; @@ -1088,13 +1182,13 @@ static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two) } if (delta && delta_size < deflate_size) { - printf("delta %lu\n", orig_size); + fprintf(file, "delta %lu\n", orig_size); free(deflated); data = delta; data_size = delta_size; } else { - printf("literal %lu\n", two->size); + fprintf(file, "literal %lu\n", two->size); free(delta); data = deflated; data_size = deflate_size; @@ -1112,17 +1206,18 @@ static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two) line[0] = bytes - 26 + 'a' - 1; encode_85(line + 1, cp, bytes); cp = (char *) cp + bytes; - puts(line); + fputs(line, file); + fputc('\n', file); } - printf("\n"); + fprintf(file, "\n"); free(data); } -static void emit_binary_diff(mmfile_t *one, mmfile_t *two) +static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two) { - printf("GIT binary patch\n"); - emit_binary_diff_body(one, two); - emit_binary_diff_body(two, one); + fprintf(file, "GIT binary patch\n"); + emit_binary_diff_body(file, one, two); + emit_binary_diff_body(file, two, one); } static void setup_diff_attr_check(struct git_attr_check *check) @@ -1199,7 +1294,7 @@ static struct builtin_funcname_pattern { "new\\|return\\|switch\\|throw\\|while\\)\n" "^[ ]*\\(\\([ ]*" "[A-Za-z_][A-Za-z_0-9]*\\)\\{2,\\}" - "[ ]*([^;]*$\\)" }, + "[ ]*([^;]*\\)$" }, { "tex", "^\\(\\\\\\(sub\\)*section{.*\\)$" }, }; @@ -1253,25 +1348,25 @@ static void builtin_diff(const char *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); + fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset); if (lbl[0][0] == '/') { /* /dev/null */ - printf("%snew file mode %06o%s\n", set, two->mode, reset); + fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset); if (xfrm_msg && xfrm_msg[0]) - printf("%s%s%s\n", set, xfrm_msg, reset); + fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); } else if (lbl[1][0] == '/') { - printf("%sdeleted file mode %06o%s\n", set, one->mode, reset); + fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset); if (xfrm_msg && xfrm_msg[0]) - printf("%s%s%s\n", set, xfrm_msg, reset); + fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); } else { if (one->mode != two->mode) { - printf("%sold mode %06o%s\n", set, one->mode, reset); - printf("%snew mode %06o%s\n", set, two->mode, reset); + fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset); + fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset); } if (xfrm_msg && xfrm_msg[0]) - printf("%s%s%s\n", set, xfrm_msg, reset); + fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset); /* * we do not run diff between different kind * of objects. @@ -1295,10 +1390,10 @@ static void builtin_diff(const char *name_a, !memcmp(mf1.ptr, mf2.ptr, mf1.size)) goto free_ab_and_return; if (DIFF_OPT_TST(o, BINARY)) - emit_binary_diff(&mf1, &mf2); + emit_binary_diff(o->file, &mf1, &mf2); else - printf("Binary files %s and %s differ\n", - lbl[0], lbl[1]); + fprintf(o->file, "Binary files %s and %s differ\n", + lbl[0], lbl[1]); o->found_changes = 1; } else { @@ -1320,6 +1415,7 @@ static void builtin_diff(const char *name_a, 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); + ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; xecfg.flags = XDL_EMIT_FUNCNAMES; @@ -1334,9 +1430,11 @@ static void builtin_diff(const char *name_a, ecb.outf = xdiff_outf; ecb.priv = &ecbdata; ecbdata.xm.consume = fn_out_consume; - if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) + if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) { ecbdata.diff_words = xcalloc(1, sizeof(struct diff_words_data)); + ecbdata.diff_words->file = o->file; + } xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) free_diff_words_data(&ecbdata); @@ -1399,6 +1497,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b, } static void builtin_checkdiff(const char *name_a, const char *name_b, + const char *attr_path, struct diff_filespec *one, struct diff_filespec *two, struct diff_options *o) { @@ -1413,7 +1512,8 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, 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); + data.ws_rule = whitespace_rule(attr_path); + data.file = o->file; if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); @@ -1838,6 +1938,9 @@ static const char *external_diff_attr(const char *name) { struct git_attr_check attr_diff_check; + if (!name) + return NULL; + setup_diff_attr_check(&attr_diff_check); if (!git_checkattr(name, 1, &attr_diff_check)) { const char *value = attr_diff_check.value; @@ -1857,6 +1960,7 @@ static const char *external_diff_attr(const char *name) static void run_diff_cmd(const char *pgm, const char *name, const char *other, + const char *attr_path, struct diff_filespec *one, struct diff_filespec *two, const char *xfrm_msg, @@ -1866,7 +1970,7 @@ static void run_diff_cmd(const char *pgm, if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL)) pgm = NULL; else { - const char *cmd = external_diff_attr(name); + const char *cmd = external_diff_attr(attr_path); if (cmd) pgm = cmd; } @@ -1880,7 +1984,7 @@ static void run_diff_cmd(const char *pgm, builtin_diff(name, other ? other : name, one, two, xfrm_msg, o, complete_rewrite); else - printf("* Unmerged path %s\n", name); + fprintf(o->file, "* Unmerged path %s\n", name); } static void diff_fill_sha1_info(struct diff_filespec *one) @@ -1907,6 +2011,15 @@ static int similarity_index(struct diff_filepair *p) return p->score * 100 / MAX_SCORE; } +static void strip_prefix(int prefix_length, const char **namep, const char **otherp) +{ + /* Strip the prefix but do not molest /dev/null and absolute paths */ + if (*namep && **namep != '/') + *namep += prefix_length; + if (*otherp && **otherp != '/') + *otherp += prefix_length; +} + static void run_diff(struct diff_filepair *p, struct diff_options *o) { const char *pgm = external_diff(); @@ -1916,16 +2029,21 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) struct diff_filespec *two = p->two; const char *name; const char *other; + const char *attr_path; int complete_rewrite = 0; + name = p->one->path; + other = (strcmp(name, p->two->path) ? p->two->path : NULL); + attr_path = name; + if (o->prefix_length) + strip_prefix(o->prefix_length, &name, &other); if (DIFF_PAIR_UNMERGED(p)) { - run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, o, 0); + run_diff_cmd(pgm, name, NULL, attr_path, + NULL, NULL, NULL, o, 0); return; } - name = p->one->path; - other = (strcmp(name, p->two->path) ? p->two->path : NULL); diff_fill_sha1_info(one); diff_fill_sha1_info(two); @@ -1988,15 +2106,17 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) * needs to be split into deletion and creation. */ struct diff_filespec *null = alloc_filespec(two->path); - run_diff_cmd(NULL, name, other, one, null, xfrm_msg, o, 0); + run_diff_cmd(NULL, name, other, attr_path, + one, null, xfrm_msg, o, 0); free(null); null = alloc_filespec(one->path); - run_diff_cmd(NULL, name, other, null, two, xfrm_msg, o, 0); + run_diff_cmd(NULL, name, other, attr_path, + null, two, xfrm_msg, o, 0); free(null); } else - run_diff_cmd(pgm, name, other, one, two, xfrm_msg, o, - complete_rewrite); + run_diff_cmd(pgm, name, other, attr_path, + one, two, xfrm_msg, o, complete_rewrite); strbuf_release(&msg); } @@ -2017,6 +2137,9 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, name = p->one->path; other = (strcmp(name, p->two->path) ? p->two->path : NULL); + if (o->prefix_length) + strip_prefix(o->prefix_length, &name, &other); + diff_fill_sha1_info(p->one); diff_fill_sha1_info(p->two); @@ -2029,6 +2152,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) { const char *name; const char *other; + const char *attr_path; if (DIFF_PAIR_UNMERGED(p)) { /* unmerged */ @@ -2037,25 +2161,33 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) name = p->one->path; other = (strcmp(name, p->two->path) ? p->two->path : NULL); + attr_path = other ? other : name; + + if (o->prefix_length) + strip_prefix(o->prefix_length, &name, &other); diff_fill_sha1_info(p->one); diff_fill_sha1_info(p->two); - builtin_checkdiff(name, other, p->one, p->two, o); + builtin_checkdiff(name, other, attr_path, p->one, p->two, o); } void diff_setup(struct diff_options *options) { memset(options, 0, sizeof(*options)); + + options->file = stdout; + options->line_termination = '\n'; options->break_opt = -1; options->rename_limit = -1; + options->dirstat_percent = 3; options->context = 3; options->msg_sep = ""; options->change = diff_change; options->add_remove = diff_addremove; - if (diff_use_color_default) + if (diff_use_color_default > 0) DIFF_OPT_SET(options, COLOR_DIFF); else DIFF_OPT_CLR(options, COLOR_DIFF); @@ -2083,6 +2215,13 @@ int diff_setup_done(struct diff_options *options) if (DIFF_OPT_TST(options, FIND_COPIES_HARDER)) options->detect_rename = DIFF_DETECT_COPY; + if (!DIFF_OPT_TST(options, RELATIVE_NAME)) + options->prefix = NULL; + if (options->prefix) + options->prefix_length = strlen(options->prefix); + else + options->prefix_length = 0; + if (options->output_format & (DIFF_FORMAT_NAME | DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_CHECKDIFF | @@ -2091,6 +2230,7 @@ int diff_setup_done(struct diff_options *options) DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SHORTSTAT | + DIFF_FORMAT_DIRSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH); @@ -2102,6 +2242,7 @@ int diff_setup_done(struct diff_options *options) DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SHORTSTAT | + DIFF_FORMAT_DIRSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_CHECKDIFF)) DIFF_OPT_SET(options, RECURSIVE); @@ -2212,6 +2353,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->output_format |= DIFF_FORMAT_NUMSTAT; else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; + else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent)) + options->output_format |= DIFF_FORMAT_DIRSTAT; + else if (!strcmp(arg, "--cumulative")) + options->output_format |= DIFF_FORMAT_CUMULATIVE; else if (!strcmp(arg, "--check")) options->output_format |= DIFF_FORMAT_CHECKDIFF; else if (!strcmp(arg, "--summary")) @@ -2271,6 +2416,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } else if (!strcmp(arg, "--no-renames")) options->detect_rename = 0; + else if (!strcmp(arg, "--relative")) + DIFF_OPT_SET(options, RELATIVE_NAME); + else if (!prefixcmp(arg, "--relative=")) { + DIFF_OPT_SET(options, RELATIVE_NAME); + options->prefix = arg + 11; + } /* xdiff options */ else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space")) @@ -2340,7 +2491,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->b_prefix = arg + 13; else if (!strcmp(arg, "--no-prefix")) options->a_prefix = options->b_prefix = ""; - else + else if (!prefixcmp(arg, "--output=")) { + options->file = fopen(arg + strlen("--output="), "w"); + options->close_file = 1; + } else return 0; return 1; } @@ -2451,8 +2605,6 @@ const char *diff_unique_abbrev(const unsigned char *sha1, int len) return sha1_to_hex(sha1); abbrev = find_unique_abbrev(sha1, len); - if (!abbrev) - return sha1_to_hex(sha1); abblen = strlen(abbrev); if (abblen < 37) { static char hex[41]; @@ -2471,23 +2623,31 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) int inter_name_termination = line_termination ? '\t' : '\0'; if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) { - printf(":%06o %06o %s ", p->one->mode, p->two->mode, - diff_unique_abbrev(p->one->sha1, opt->abbrev)); - printf("%s ", diff_unique_abbrev(p->two->sha1, opt->abbrev)); + fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode, + diff_unique_abbrev(p->one->sha1, opt->abbrev)); + fprintf(opt->file, "%s ", diff_unique_abbrev(p->two->sha1, opt->abbrev)); } if (p->score) { - printf("%c%03d%c", p->status, similarity_index(p), - inter_name_termination); + fprintf(opt->file, "%c%03d%c", p->status, similarity_index(p), + inter_name_termination); } else { - printf("%c%c", p->status, inter_name_termination); + fprintf(opt->file, "%c%c", p->status, inter_name_termination); } - if (p->status == DIFF_STATUS_COPIED || p->status == DIFF_STATUS_RENAMED) { - write_name_quoted(p->one->path, stdout, inter_name_termination); - write_name_quoted(p->two->path, stdout, line_termination); + if (p->status == DIFF_STATUS_COPIED || + p->status == DIFF_STATUS_RENAMED) { + const char *name_a, *name_b; + name_a = p->one->path; + name_b = p->two->path; + strip_prefix(opt->prefix_length, &name_a, &name_b); + write_name_quoted(name_a, opt->file, inter_name_termination); + write_name_quoted(name_b, opt->file, line_termination); } else { - const char *path = p->one->mode ? p->one->path : p->two->path; - write_name_quoted(path, stdout, line_termination); + const char *name_a, *name_b; + name_a = p->one->mode ? p->one->path : p->two->path; + name_b = NULL; + strip_prefix(opt->prefix_length, &name_a, &name_b); + write_name_quoted(name_a, opt->file, line_termination); } } @@ -2684,62 +2844,67 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt) diff_flush_checkdiff(p, opt); else if (fmt & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS)) diff_flush_raw(p, opt); - else if (fmt & DIFF_FORMAT_NAME) - write_name_quoted(p->two->path, stdout, opt->line_termination); + else if (fmt & DIFF_FORMAT_NAME) { + const char *name_a, *name_b; + name_a = p->two->path; + name_b = NULL; + strip_prefix(opt->prefix_length, &name_a, &name_b); + write_name_quoted(name_a, opt->file, opt->line_termination); + } } -static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs) +static void show_file_mode_name(FILE *file, const char *newdelete, struct diff_filespec *fs) { if (fs->mode) - printf(" %s mode %06o ", newdelete, fs->mode); + fprintf(file, " %s mode %06o ", newdelete, fs->mode); else - printf(" %s ", newdelete); - write_name_quoted(fs->path, stdout, '\n'); + fprintf(file, " %s ", newdelete); + write_name_quoted(fs->path, file, '\n'); } -static void show_mode_change(struct diff_filepair *p, int show_name) +static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name) { if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) { - printf(" mode change %06o => %06o%c", p->one->mode, p->two->mode, + fprintf(file, " mode change %06o => %06o%c", p->one->mode, p->two->mode, show_name ? ' ' : '\n'); if (show_name) { - write_name_quoted(p->two->path, stdout, '\n'); + write_name_quoted(p->two->path, file, '\n'); } } } -static void show_rename_copy(const char *renamecopy, struct diff_filepair *p) +static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p) { char *names = pprint_rename(p->one->path, p->two->path); - printf(" %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); + fprintf(file, " %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); free(names); - show_mode_change(p, 0); + show_mode_change(file, p, 0); } -static void diff_summary(struct diff_filepair *p) +static void diff_summary(FILE *file, struct diff_filepair *p) { switch(p->status) { case DIFF_STATUS_DELETED: - show_file_mode_name("delete", p->one); + show_file_mode_name(file, "delete", p->one); break; case DIFF_STATUS_ADDED: - show_file_mode_name("create", p->two); + show_file_mode_name(file, "create", p->two); break; case DIFF_STATUS_COPIED: - show_rename_copy("copy", p); + show_rename_copy(file, "copy", p); break; case DIFF_STATUS_RENAMED: - show_rename_copy("rename", p); + show_rename_copy(file, "rename", p); break; default: if (p->score) { - fputs(" rewrite ", stdout); - write_name_quoted(p->two->path, stdout, ' '); - printf("(%d%%)\n", similarity_index(p)); + fputs(" rewrite ", file); + write_name_quoted(p->two->path, file, ' '); + fprintf(file, "(%d%%)\n", similarity_index(p)); } - show_mode_change(p, !p->score); + show_mode_change(file, p, !p->score); break; } } @@ -2930,7 +3095,7 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) { + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIRSTAT)) { struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); @@ -2940,19 +3105,21 @@ void diff_flush(struct diff_options *options) if (check_pair_status(p)) diff_flush_stat(p, options, &diffstat); } + if (output_format & DIFF_FORMAT_DIRSTAT) + show_dirstat(&diffstat, options); if (output_format & DIFF_FORMAT_NUMSTAT) show_numstat(&diffstat, options); if (output_format & DIFF_FORMAT_DIFFSTAT) show_stats(&diffstat, options); if (output_format & DIFF_FORMAT_SHORTSTAT) - show_shortstats(&diffstat); + show_shortstats(&diffstat, options); free_diffstat_info(&diffstat); separator++; } if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) { for (i = 0; i < q->nr; i++) - diff_summary(q->queue[i]); + diff_summary(options->file, q->queue[i]); separator++; } @@ -2960,9 +3127,9 @@ void diff_flush(struct diff_options *options) if (separator) { if (options->stat_sep) { /* attach patch instead of inline */ - fputs(options->stat_sep, stdout); + fputs(options->stat_sep, options->file); } else { - putchar(options->line_termination); + putc(options->line_termination, options->file); } } @@ -2982,6 +3149,8 @@ free_queue: free(q->queue); q->queue = NULL; q->nr = q->alloc = 0; + if (options->close_file) + fclose(options->file); } static void diffcore_apply_filter(const char *filter) @@ -3044,11 +3213,8 @@ static void diffcore_apply_filter(const char *filter) static int diff_filespec_is_identical(struct diff_filespec *one, struct diff_filespec *two) { - if (S_ISGITLINK(one->mode)) { - diff_fill_sha1_info(one); - diff_fill_sha1_info(two); - return !hashcmp(one->sha1, two->sha1); - } + if (S_ISGITLINK(one->mode)) + return 0; if (diff_populate_filespec(one, 0)) return 0; if (diff_populate_filespec(two, 0)) @@ -3171,6 +3337,11 @@ void diff_addremove(struct diff_options *options, if (!path) path = ""; sprintf(concatpath, "%s%s", base, path); + + if (options->prefix && + strncmp(concatpath, options->prefix, options->prefix_length)) + return; + one = alloc_filespec(concatpath); two = alloc_filespec(concatpath); @@ -3200,6 +3371,11 @@ void diff_change(struct diff_options *options, } if (!path) path = ""; sprintf(concatpath, "%s%s", base, path); + + if (options->prefix && + strncmp(concatpath, options->prefix, options->prefix_length)) + return; + one = alloc_filespec(concatpath); two = alloc_filespec(concatpath); fill_filespec(one, old_sha1, old_mode); @@ -3214,6 +3390,11 @@ void diff_unmerge(struct diff_options *options, unsigned mode, const unsigned char *sha1) { struct diff_filespec *one, *two; + + if (options->prefix && + strncmp(path, options->prefix, options->prefix_length)) + return; + one = alloc_filespec(path); two = alloc_filespec(path); fill_filespec(one, sha1, mode);