Code

Merge branch 'nd/maint-parse-depth' into maint
[git.git] / diff.c
diff --git a/diff.c b/diff.c
index 559bf574a88f97402f32e1bd4ab51f6f0c9d5026..374ecf3b486e218c52f94ba14481a715c19b80db 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -31,6 +31,7 @@ static const char *external_diff_cmd_cfg;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
 static int diff_no_prefix;
+static int diff_dirstat_permille_default = 30;
 static struct diff_options default_diff_options;
 
 static char diff_colors[][COLOR_MAXLEN] = {
@@ -66,6 +67,58 @@ static int parse_diff_color_slot(const char *var, int ofs)
        return -1;
 }
 
+static int parse_dirstat_params(struct diff_options *options, const char *params,
+                               struct strbuf *errmsg)
+{
+       const char *p = params;
+       int p_len, ret = 0;
+
+       while (*p) {
+               p_len = strchrnul(p, ',') - p;
+               if (!memcmp(p, "changes", p_len)) {
+                       DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
+                       DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
+               } else if (!memcmp(p, "lines", p_len)) {
+                       DIFF_OPT_SET(options, DIRSTAT_BY_LINE);
+                       DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
+               } else if (!memcmp(p, "files", p_len)) {
+                       DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
+                       DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
+               } else if (!memcmp(p, "noncumulative", p_len)) {
+                       DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
+               } else if (!memcmp(p, "cumulative", p_len)) {
+                       DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
+               } else if (isdigit(*p)) {
+                       char *end;
+                       int permille = strtoul(p, &end, 10) * 10;
+                       if (*end == '.' && isdigit(*++end)) {
+                               /* only use first digit */
+                               permille += *end - '0';
+                               /* .. and ignore any further digits */
+                               while (isdigit(*++end))
+                                       ; /* nothing */
+                       }
+                       if (end - p == p_len)
+                               options->dirstat_permille = permille;
+                       else {
+                               strbuf_addf(errmsg, _("  Failed to parse dirstat cut-off percentage '%.*s'\n"),
+                                           p_len, p);
+                               ret++;
+                       }
+               } else {
+                       strbuf_addf(errmsg, _("  Unknown dirstat parameter '%.*s'\n"),
+                                   p_len, p);
+                       ret++;
+               }
+
+               p += p_len;
+
+               if (*p)
+                       p++; /* more parameters, swallow separator */
+       }
+       return ret;
+}
+
 static int git_config_rename(const char *var, const char *value)
 {
        if (!value)
@@ -84,7 +137,7 @@ static int git_config_rename(const char *var, const char *value)
 int git_diff_ui_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
-               diff_use_color_default = git_config_colorbool(var, value, -1);
+               diff_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
        if (!strcmp(var, "diff.renames")) {
@@ -111,6 +164,9 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
        if (!strcmp(var, "diff.ignoresubmodules"))
                handle_ignore_submodules_arg(&default_diff_options, value);
 
+       if (git_color_config(var, value, cb) < 0)
+               return -1;
+
        return git_diff_basic_config(var, value, cb);
 }
 
@@ -145,10 +201,21 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (!strcmp(var, "diff.dirstat")) {
+               struct strbuf errmsg = STRBUF_INIT;
+               default_diff_options.dirstat_permille = diff_dirstat_permille_default;
+               if (parse_dirstat_params(&default_diff_options, value, &errmsg))
+                       warning(_("Found errors in 'diff.dirstat' config variable:\n%s"),
+                               errmsg.buf);
+               strbuf_release(&errmsg);
+               diff_dirstat_permille_default = default_diff_options.dirstat_permille;
+               return 0;
+       }
+
        if (!prefixcmp(var, "submodule."))
                return parse_submodule_config_option(var, value);
 
-       return git_color_default_config(var, value, cb);
+       return git_default_config(var, value, cb);
 }
 
 static char *quote_two(const char *one, const char *two)
@@ -519,11 +586,10 @@ static void emit_rewrite_diff(const char *name_a,
                              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 *reset = diff_get_color(color_diff, DIFF_RESET);
+       const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO);
+       const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO);
+       const char *reset = diff_get_color(o->use_color, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
        const char *a_prefix, *b_prefix;
        char *data_one, *data_two;
@@ -559,7 +625,7 @@ static void emit_rewrite_diff(const char *name_a,
        size_two = fill_textconv(textconv_two, two, &data_two);
 
        memset(&ecbdata, 0, sizeof(ecbdata));
-       ecbdata.color_diff = color_diff;
+       ecbdata.color_diff = want_color(o->use_color);
        ecbdata.found_changesp = &o->found_changes;
        ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
        ecbdata.opt = o;
@@ -581,11 +647,14 @@ static void emit_rewrite_diff(const char *name_a,
                line_prefix, metainfo, a_name.buf, name_a_tab, reset,
                line_prefix, metainfo, b_name.buf, name_b_tab, reset,
                line_prefix, fraginfo);
-       print_line_count(o->file, lc_a);
+       if (!o->irreversible_delete)
+               print_line_count(o->file, lc_a);
+       else
+               fprintf(o->file, "?,?");
        fprintf(o->file, " +");
        print_line_count(o->file, lc_b);
        fprintf(o->file, " @@%s\n", reset);
-       if (lc_a)
+       if (lc_a && !o->irreversible_delete)
                emit_rewrite_lines(&ecbdata, '-', data_one, size_one);
        if (lc_b)
                emit_rewrite_lines(&ecbdata, '+', data_two, size_two);
@@ -937,7 +1006,7 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
 
 const char *diff_get_color(int diff_use_color, enum color_diff ix)
 {
-       if (diff_use_color)
+       if (want_color(diff_use_color))
                return diff_colors[ix];
        return "";
 }
@@ -1249,9 +1318,10 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        int i, len, add, del, adds = 0, dels = 0;
        uintmax_t max_change = 0, max_len = 0;
        int total_files = data->nr;
-       int width, name_width;
+       int width, name_width, count;
        const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
+       int extra_shown = 0;
        struct strbuf *msg = NULL;
 
        if (data->nr == 0)
@@ -1264,6 +1334,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 
        width = options->stat_width ? options->stat_width : 80;
        name_width = options->stat_name_width ? options->stat_name_width : 50;
+       count = options->stat_count ? options->stat_count : data->nr;
 
        /* Sanity: give at least 5 columns to the graph,
         * but leave at least 10 columns for the name.
@@ -1280,9 +1351,14 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
        del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
 
-       for (i = 0; i < data->nr; i++) {
+       for (i = 0; (i < count) && (i < data->nr); i++) {
                struct diffstat_file *file = data->files[i];
                uintmax_t change = file->added + file->deleted;
+               if (!data->files[i]->is_renamed &&
+                        (change == 0)) {
+                       count++; /* not shown == room for one more */
+                       continue;
+               }
                fill_print_name(file);
                len = strlen(file->print_name);
                if (max_len < len)
@@ -1293,6 +1369,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                if (max_change < change)
                        max_change = change;
        }
+       count = i; /* min(count, data->nr) */
 
        /* Compute the width of the graph part;
         * 10 is for one blank at the beginning of the line plus
@@ -1307,13 +1384,18 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        else
                width = max_change;
 
-       for (i = 0; i < data->nr; i++) {
+       for (i = 0; i < count; i++) {
                const char *prefix = "";
                char *name = data->files[i]->print_name;
                uintmax_t added = data->files[i]->added;
                uintmax_t deleted = data->files[i]->deleted;
                int name_len;
 
+               if (!data->files[i]->is_renamed &&
+                        (added + deleted == 0)) {
+                       total_files--;
+                       continue;
+               }
                /*
                 * "scale" the filename
                 */
@@ -1348,11 +1430,6 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                        fprintf(options->file, "  Unmerged\n");
                        continue;
                }
-               else if (!data->files[i]->is_renamed &&
-                        (added + deleted == 0)) {
-                       total_files--;
-                       continue;
-               }
 
                /*
                 * scale the add/delete
@@ -1374,6 +1451,20 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
        }
+       for (i = count; i < data->nr; i++) {
+               uintmax_t added = data->files[i]->added;
+               uintmax_t deleted = data->files[i]->deleted;
+               if (!data->files[i]->is_renamed &&
+                        (added + deleted == 0)) {
+                       total_files--;
+                       continue;
+               }
+               adds += added;
+               dels += deleted;
+               if (!extra_shown)
+                       fprintf(options->file, "%s ...\n", line_prefix);
+               extra_shown = 1;
+       }
        fprintf(options->file, "%s", line_prefix);
        fprintf(options->file,
               " %d files changed, %d insertions(+), %d deletions(-)\n",
@@ -1460,7 +1551,7 @@ struct dirstat_file {
 
 struct dirstat_dir {
        struct dirstat_file *files;
-       int alloc, nr, percent, cumulative;
+       int alloc, nr, permille, cumulative;
 };
 
 static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
@@ -1507,12 +1598,11 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
         *    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) {
+               if (this_dir) {
+                       int permille = this_dir * 1000 / changed;
+                       if (permille >= dir->permille) {
                                fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix,
-                                       percent, permille % 10, baselen, base);
+                                       permille / 10, permille % 10, baselen, base);
                                if (!dir->cumulative)
                                        return 0;
                        }
@@ -1538,7 +1628,7 @@ static void show_dirstat(struct diff_options *options)
        dir.files = NULL;
        dir.alloc = 0;
        dir.nr = 0;
-       dir.percent = options->dirstat_percent;
+       dir.permille = options->dirstat_permille;
        dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
 
        changed = 0;
@@ -1627,6 +1717,50 @@ found_damage:
        gather_dirstat(options, &dir, changed, "", 0);
 }
 
+static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *options)
+{
+       int i;
+       unsigned long changed;
+       struct dirstat_dir dir;
+
+       if (data->nr == 0)
+               return;
+
+       dir.files = NULL;
+       dir.alloc = 0;
+       dir.nr = 0;
+       dir.permille = options->dirstat_permille;
+       dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
+
+       changed = 0;
+       for (i = 0; i < data->nr; i++) {
+               struct diffstat_file *file = data->files[i];
+               unsigned long damage = file->added + file->deleted;
+               if (file->is_binary)
+                       /*
+                        * binary files counts bytes, not lines. Must find some
+                        * way to normalize binary bytes vs. textual lines.
+                        * The following heuristic assumes that there are 64
+                        * bytes per "line".
+                        * This is stupid and ugly, but very cheap...
+                        */
+                       damage = (damage + 63) / 64;
+               ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
+               dir.files[dir.nr].name = file->name;
+               dir.files[dir.nr].changed = damage;
+               changed += damage;
+               dir.nr++;
+       }
+
+       /* This can happen even with many files, if everything was renames */
+       if (!changed)
+               return;
+
+       /* Show all directories with more than x% of the changes */
+       qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
+       gather_dirstat(options, &dir, changed, "", 0);
+}
+
 static void free_diffstat_info(struct diffstat_t *diffstat)
 {
        int i;
@@ -1676,11 +1810,10 @@ static int is_conflict_marker(const char *line, int marker_size, unsigned long l
 static void checkdiff_consume(void *priv, char *line, unsigned long len)
 {
        struct checkdiff_t *data = priv;
-       int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
        int marker_size = data->conflict_marker_size;
-       const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE);
-       const char *reset = diff_get_color(color_diff, DIFF_RESET);
-       const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
+       const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE);
+       const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
+       const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW);
        char *err;
        char *line_prefix = "";
        struct strbuf *msgbuf;
@@ -1729,20 +1862,20 @@ static unsigned char *deflate_it(char *data,
 {
        int bound;
        unsigned char *deflated;
-       z_stream stream;
+       git_zstream stream;
 
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, zlib_compression_level);
-       bound = deflateBound(&stream, size);
+       git_deflate_init(&stream, zlib_compression_level);
+       bound = git_deflate_bound(&stream, size);
        deflated = xmalloc(bound);
        stream.next_out = deflated;
        stream.avail_out = bound;
 
        stream.next_in = (unsigned char *)data;
        stream.avail_in = size;
-       while (deflate(&stream, Z_FINISH) == Z_OK)
+       while (git_deflate(&stream, Z_FINISH) == Z_OK)
                ; /* nothing */
-       deflateEnd(&stream);
+       git_deflate_end(&stream);
        *result_size = stream.total_out;
        return deflated;
 }
@@ -1874,19 +2007,7 @@ struct userdiff_driver *get_textconv(struct diff_filespec *one)
                return NULL;
 
        diff_filespec_load_driver(one);
-       if (!one->driver->textconv)
-               return NULL;
-
-       if (one->driver->textconv_want_cache && !one->driver->textconv_cache) {
-               struct notes_cache *c = xmalloc(sizeof(*c));
-               struct strbuf name = STRBUF_INIT;
-
-               strbuf_addf(&name, "textconv/%s", one->driver->name);
-               notes_cache_init(c, name.buf, one->driver->textconv);
-               one->driver->textconv_cache = c;
-       }
-
-       return one->driver;
+       return userdiff_get_textconv(one->driver);
 }
 
 static void builtin_diff(const char *name_a,
@@ -1989,7 +2110,11 @@ static void builtin_diff(const char *name_a,
                }
        }
 
-       if (!DIFF_OPT_TST(o, TEXT) &&
+       if (o->irreversible_delete && lbl[1][0] == '/') {
+               fprintf(o->file, "%s", header.buf);
+               strbuf_reset(&header);
+               goto free_ab_and_return;
+       } else if (!DIFF_OPT_TST(o, TEXT) &&
            ( (!textconv_one && diff_filespec_is_binary(one)) ||
              (!textconv_two && diff_filespec_is_binary(two)) )) {
                if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
@@ -2009,8 +2134,7 @@ static void builtin_diff(const char *name_a,
                        fprintf(o->file, "%sBinary files %s and %s differ\n",
                                line_prefix, lbl[0], lbl[1]);
                o->found_changes = 1;
-       }
-       else {
+       } else {
                /* Crazy xdl interfaces.. */
                const char *diffopts = getenv("GIT_DIFF_OPTS");
                xpparam_t xpp;
@@ -2034,7 +2158,7 @@ static void builtin_diff(const char *name_a,
                memset(&xecfg, 0, sizeof(xecfg));
                memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
-               ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
+               ecbdata.color_diff = want_color(o->use_color);
                ecbdata.found_changesp = &o->found_changes;
                ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
@@ -2045,6 +2169,8 @@ static void builtin_diff(const char *name_a,
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
+               if (DIFF_OPT_TST(o, FUNCCONTEXT))
+                       xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
                if (pe)
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
                if (!diffopts)
@@ -2082,7 +2208,7 @@ static void builtin_diff(const char *name_a,
                                        break;
                                }
                        }
-                       if (DIFF_OPT_TST(o, COLOR_DIFF)) {
+                       if (want_color(o->use_color)) {
                                struct diff_words_style *st = ecbdata.diff_words->style;
                                st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
                                st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
@@ -2150,6 +2276,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = o->xdl_opts;
+               xecfg.ctxlen = o->context;
+               xecfg.interhunkctxlen = o->interhunkcontext;
                xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
                              &xpp, &xecfg);
        }
@@ -2732,7 +2860,7 @@ static void run_diff_cmd(const char *pgm,
                 */
                fill_metainfo(msg, name, other, one, two, o, p,
                              &must_show_header,
-                             DIFF_OPT_TST(o, COLOR_DIFF) && !pgm);
+                             want_color(o->use_color) && !pgm);
                xfrm_msg = msg->len ? msg->buf : NULL;
        }
 
@@ -2893,13 +3021,12 @@ void diff_setup(struct diff_options *options)
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
-       options->dirstat_percent = 3;
+       options->dirstat_permille = diff_dirstat_permille_default;
        options->context = 3;
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
-       if (diff_use_color_default > 0)
-               DIFF_OPT_SET(options, COLOR_DIFF);
+       options->use_color = diff_use_color_default;
        options->detect_rename = diff_detect_rename_default;
 
        if (diff_no_prefix) {
@@ -3107,6 +3234,7 @@ static int stat_opt(struct diff_options *options, const char **av)
        char *end;
        int width = options->stat_width;
        int name_width = options->stat_name_width;
+       int count = options->stat_count;
        int argcount = 1;
 
        arg += strlen("--stat");
@@ -3134,12 +3262,24 @@ static int stat_opt(struct diff_options *options, const char **av)
                                name_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
+               } else if (!prefixcmp(arg, "-count")) {
+                       arg += strlen("-count");
+                       if (*arg == '=')
+                               count = strtoul(arg + 1, &end, 10);
+                       else if (!*arg && !av[1])
+                               die("Option '--stat-count' requires a value");
+                       else if (!*arg) {
+                               count = strtoul(av[1], &end, 10);
+                               argcount = 2;
+                       }
                }
                break;
        case '=':
                width = strtoul(arg+1, &end, 10);
                if (*end == ',')
                        name_width = strtoul(end+1, &end, 10);
+               if (*end == ',')
+                       count = strtoul(end+1, &end, 10);
        }
 
        /* Important! This checks all the error cases! */
@@ -3148,9 +3288,25 @@ static int stat_opt(struct diff_options *options, const char **av)
        options->output_format |= DIFF_FORMAT_DIFFSTAT;
        options->stat_name_width = name_width;
        options->stat_width = width;
+       options->stat_count = count;
        return argcount;
 }
 
+static int parse_dirstat_opt(struct diff_options *options, const char *params)
+{
+       struct strbuf errmsg = STRBUF_INIT;
+       if (parse_dirstat_params(options, params, &errmsg))
+               die(_("Failed to parse --dirstat/-X option parameter:\n%s"),
+                   errmsg.buf);
+       strbuf_release(&errmsg);
+       /*
+        * The caller knows a dirstat-related option is given from the command
+        * line; allow it to say "return this_function();"
+        */
+       options->output_format |= DIFF_FORMAT_DIRSTAT;
+       return 1;
+}
+
 int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 {
        const char *arg = av[0];
@@ -3170,15 +3326,19 @@ 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_DIRSTAT;
-               DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
-       } else if (opt_arg(arg, 0, "dirstat-by-file",
-                          &options->dirstat_percent)) {
-               options->output_format |= DIFF_FORMAT_DIRSTAT;
-               DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
+       else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat"))
+               return parse_dirstat_opt(options, "");
+       else if (!prefixcmp(arg, "-X"))
+               return parse_dirstat_opt(options, arg + 2);
+       else if (!prefixcmp(arg, "--dirstat="))
+               return parse_dirstat_opt(options, arg + 10);
+       else if (!strcmp(arg, "--cumulative"))
+               return parse_dirstat_opt(options, "cumulative");
+       else if (!strcmp(arg, "--dirstat-by-file"))
+               return parse_dirstat_opt(options, "files");
+       else if (!prefixcmp(arg, "--dirstat-by-file=")) {
+               parse_dirstat_opt(options, "files");
+               return parse_dirstat_opt(options, arg + 18);
        }
        else if (!strcmp(arg, "--check"))
                options->output_format |= DIFF_FORMAT_CHECKDIFF;
@@ -3193,7 +3353,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "-s"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
        else if (!prefixcmp(arg, "--stat"))
-               /* --stat, --stat-width, or --stat-name-width */
+               /* --stat, --stat-width, --stat-name-width, or --stat-count */
                return stat_opt(options, av);
 
        /* renames options */
@@ -3208,6 +3368,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                        return error("invalid argument to -M: %s", arg+2);
                options->detect_rename = DIFF_DETECT_RENAME;
        }
+       else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) {
+               options->irreversible_delete = 1;
+       }
        else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") ||
                 !strcmp(arg, "--find-copies")) {
                if (options->detect_rename == DIFF_DETECT_COPY)
@@ -3226,6 +3389,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        }
 
        /* xdiff options */
+       else if (!strcmp(arg, "--minimal"))
+               DIFF_XDL_SET(options, NEED_MINIMAL);
+       else if (!strcmp(arg, "--no-minimal"))
+               DIFF_XDL_CLR(options, NEED_MINIMAL);
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE);
        else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
@@ -3234,6 +3401,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
        else if (!strcmp(arg, "--patience"))
                DIFF_XDL_SET(options, PATIENCE_DIFF);
+       else if (!strcmp(arg, "--histogram"))
+               DIFF_XDL_SET(options, HISTOGRAM_DIFF);
 
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
@@ -3251,24 +3420,21 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--follow"))
                DIFF_OPT_SET(options, FOLLOW_RENAMES);
        else if (!strcmp(arg, "--color"))
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
        else if (!prefixcmp(arg, "--color=")) {
-               int value = git_config_colorbool(NULL, arg+8, -1);
-               if (value == 0)
-                       DIFF_OPT_CLR(options, COLOR_DIFF);
-               else if (value > 0)
-                       DIFF_OPT_SET(options, COLOR_DIFF);
-               else
+               int value = git_config_colorbool(NULL, arg+8);
+               if (value < 0)
                        return error("option `color' expects \"always\", \"auto\", or \"never\"");
+               options->use_color = value;
        }
        else if (!strcmp(arg, "--no-color"))
-               DIFF_OPT_CLR(options, COLOR_DIFF);
+               options->use_color = 0;
        else if (!strcmp(arg, "--color-words")) {
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
        }
        else if (!prefixcmp(arg, "--color-words=")) {
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
                options->word_regex = arg + 14;
        }
@@ -3281,7 +3447,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                if (!strcmp(type, "plain"))
                        options->word_diff = DIFF_WORDS_PLAIN;
                else if (!strcmp(type, "color")) {
-                       DIFF_OPT_SET(options, COLOR_DIFF);
+                       options->use_color = 1;
                        options->word_diff = DIFF_WORDS_COLOR;
                }
                else if (!strcmp(type, "porcelain"))
@@ -3372,6 +3538,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (opt_arg(arg, '\0', "inter-hunk-context",
                         &options->interhunkcontext))
                ;
+       else if (!strcmp(arg, "-W"))
+               DIFF_OPT_SET(options, FUNCCONTEXT);
+       else if (!strcmp(arg, "--function-context"))
+               DIFF_OPT_SET(options, FUNCCONTEXT);
+       else if (!strcmp(arg, "--no-function-context"))
+               DIFF_OPT_CLR(options, FUNCCONTEXT);
        else if ((argcount = parse_long_opt("output", av, &optarg))) {
                options->file = fopen(optarg, "w");
                if (!options->file)
@@ -4022,6 +4194,7 @@ void diff_flush(struct diff_options *options)
        struct diff_queue_struct *q = &diff_queued_diff;
        int i, output_format = options->output_format;
        int separator = 0;
+       int dirstat_by_line = 0;
 
        /*
         * Order: raw, stat, summary, patch
@@ -4042,7 +4215,11 @@ 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_DIRSTAT && DIFF_OPT_TST(options, DIRSTAT_BY_LINE))
+               dirstat_by_line = 1;
+
+       if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT) ||
+           dirstat_by_line) {
                struct diffstat_t diffstat;
 
                memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -4057,10 +4234,12 @@ void diff_flush(struct diff_options *options)
                        show_stats(&diffstat, options);
                if (output_format & DIFF_FORMAT_SHORTSTAT)
                        show_shortstats(&diffstat, options);
+               if (output_format & DIFF_FORMAT_DIRSTAT)
+                       show_dirstat_by_line(&diffstat, options);
                free_diffstat_info(&diffstat);
                separator++;
        }
-       if (output_format & DIFF_FORMAT_DIRSTAT)
+       if ((output_format & DIFF_FORMAT_DIRSTAT) && !dirstat_by_line)
                show_dirstat(options);
 
        if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
@@ -4314,6 +4493,13 @@ int diff_result_code(struct diff_options *opt, int status)
        return result;
 }
 
+int diff_can_quit_early(struct diff_options *opt)
+{
+       return (DIFF_OPT_TST(opt, QUICK) &&
+               !opt->filter &&
+               DIFF_OPT_TST(opt, HAS_CHANGES));
+}
+
 /*
  * Shall changes to this submodule be ignored?
  *