sha1_object_info_extended(): hint about objects in delta-base cache
[gitweb.git] / diff.c
diff --git a/diff.c b/diff.c
index c78f7d5218def3d0546b636069107bc349d2c884..ba5f7aa2173a7494b56395bfd88ab6228b5ed1fe 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -31,7 +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_percent_default = 3;
+static int diff_dirstat_permille_default = 30;
 static struct diff_options default_diff_options;
 
 static char diff_colors[][COLOR_MAXLEN] = {
@@ -67,39 +67,56 @@ 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)
+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) {
-               if (!prefixcmp(p, "changes")) {
-                       p += 7;
+               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 (!prefixcmp(p, "files")) {
-                       p += 5;
+               } else if (!memcmp(p, "files", p_len)) {
+                       DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
                        DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
-               } else if (!prefixcmp(p, "noncumulative")) {
-                       p += 13;
+               } else if (!memcmp(p, "noncumulative", p_len)) {
                        DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
-               } else if (!prefixcmp(p, "cumulative")) {
-                       p += 10;
+               } else if (!memcmp(p, "cumulative", p_len)) {
                        DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
                } else if (isdigit(*p)) {
                        char *end;
-                       options->dirstat_percent = strtoul(p, &end, 10);
-                       p = end;
-               } else
-                       return error("Unknown --dirstat parameter '%s'", p);
-
-               if (*p) {
-                       /* more parameters, swallow separator */
-                       if (*p != ',')
-                               return error("Missing comma separator at char "
-                                       "%"PRIuMAX" of '%s'",
-                                       (uintmax_t) (p - params), params);
-                       p++;
+                       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 0;
+       return ret;
 }
 
 static int git_config_rename(const char *var, const char *value)
@@ -182,9 +199,13 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
        }
 
        if (!strcmp(var, "diff.dirstat")) {
-               default_diff_options.dirstat_percent = diff_dirstat_percent_default;
-               (void) parse_dirstat_params(&default_diff_options, value);
-               diff_dirstat_percent_default = default_diff_options.dirstat_percent;
+               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;
        }
 
@@ -1498,7 +1519,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,
@@ -1547,10 +1568,9 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
        if (baselen && sources != 1) {
                if (this_dir) {
                        int permille = this_dir * 1000 / changed;
-                       int percent = permille / 10;
-                       if (percent >= dir->percent) {
+                       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;
                        }
@@ -1576,7 +1596,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;
@@ -1665,6 +1685,50 @@ static void show_dirstat(struct diff_options *options)
        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;
@@ -2934,7 +2998,7 @@ void diff_setup(struct diff_options *options)
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
-       options->dirstat_percent = diff_dirstat_percent_default;
+       options->dirstat_permille = diff_dirstat_permille_default;
        options->context = 3;
 
        options->change = diff_change;
@@ -3194,8 +3258,11 @@ static int stat_opt(struct diff_options *options, const char **av)
 
 static int parse_dirstat_opt(struct diff_options *options, const char *params)
 {
-       if (parse_dirstat_params(options, params))
-               die("Failed to parse --dirstat/-X option parameter");
+       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();"
@@ -4082,6 +4149,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
@@ -4102,7 +4170,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));
@@ -4117,10 +4189,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)) {
@@ -4475,20 +4549,20 @@ void diff_change(struct diff_options *options,
                DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
-void diff_unmerge(struct diff_options *options,
-                 const char *path,
-                 unsigned mode, const unsigned char *sha1)
+struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path)
 {
+       struct diff_filepair *pair;
        struct diff_filespec *one, *two;
 
        if (options->prefix &&
            strncmp(path, options->prefix, options->prefix_length))
-               return;
+               return NULL;
 
        one = alloc_filespec(path);
        two = alloc_filespec(path);
-       fill_filespec(one, sha1, mode);
-       diff_queue(&diff_queued_diff, one, two)->is_unmerged = 1;
+       pair = diff_queue(&diff_queued_diff, one, two);
+       pair->is_unmerged = 1;
+       return pair;
 }
 
 static char *run_textconv(const char *pgm, struct diff_filespec *spec,