Merge branch 'nd/worktree-lock'
[gitweb.git] / diff.c
diff --git a/diff.c b/diff.c
index 7a4309397cb7fd6819d6f643b259c2bc3abe202f..8981477c436dd11d97714f0ad7e1b9363e328798 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -18,6 +18,7 @@
 #include "ll-merge.h"
 #include "string-list.h"
 #include "argv-array.h"
+#include "graph.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -26,6 +27,7 @@
 #endif
 
 static int diff_detect_rename_default;
+static int diff_indent_heuristic; /* experimental */
 static int diff_compaction_heuristic; /* experimental */
 static int diff_rename_limit_default = 400;
 static int diff_suppress_blank_empty;
@@ -41,6 +43,7 @@ static int diff_stat_graph_width;
 static int diff_dirstat_permille_default = 30;
 static struct diff_options default_diff_options;
 static long diff_algorithm;
+static unsigned ws_error_highlight_default = WSEH_NEW;
 
 static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
@@ -54,6 +57,11 @@ static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_NORMAL,       /* FUNCINFO */
 };
 
+static NORETURN void die_want_option(const char *option_name)
+{
+       die(_("option '%s' requires a value"), option_name);
+}
+
 static int parse_diff_color_slot(const char *var)
 {
        if (!strcasecmp(var, "context") || !strcasecmp(var, "plain"))
@@ -131,9 +139,11 @@ static int parse_dirstat_params(struct diff_options *options, const char *params
 static int parse_submodule_params(struct diff_options *options, const char *value)
 {
        if (!strcmp(value, "log"))
-               DIFF_OPT_SET(options, SUBMODULE_LOG);
+               options->submodule_format = DIFF_SUBMODULE_LOG;
        else if (!strcmp(value, "short"))
-               DIFF_OPT_CLR(options, SUBMODULE_LOG);
+               options->submodule_format = DIFF_SUBMODULE_SHORT;
+       else if (!strcmp(value, "diff"))
+               options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF;
        else
                return -1;
        return 0;
@@ -163,6 +173,43 @@ long parse_algorithm_value(const char *value)
        return -1;
 }
 
+static int parse_one_token(const char **arg, const char *token)
+{
+       const char *rest;
+       if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) {
+               *arg = rest;
+               return 1;
+       }
+       return 0;
+}
+
+static int parse_ws_error_highlight(const char *arg)
+{
+       const char *orig_arg = arg;
+       unsigned val = 0;
+
+       while (*arg) {
+               if (parse_one_token(&arg, "none"))
+                       val = 0;
+               else if (parse_one_token(&arg, "default"))
+                       val = WSEH_NEW;
+               else if (parse_one_token(&arg, "all"))
+                       val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT;
+               else if (parse_one_token(&arg, "new"))
+                       val |= WSEH_NEW;
+               else if (parse_one_token(&arg, "old"))
+                       val |= WSEH_OLD;
+               else if (parse_one_token(&arg, "context"))
+                       val |= WSEH_CONTEXT;
+               else {
+                       return -1 - (int)(arg - orig_arg);
+               }
+               if (*arg)
+                       arg++;
+       }
+       return val;
+}
+
 /*
  * These are to give UI layer defaults.
  * The core-level commands such as git-diff-files should
@@ -174,6 +221,21 @@ void init_diff_ui_defaults(void)
        diff_detect_rename_default = 1;
 }
 
+int git_diff_heuristic_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "diff.indentheuristic")) {
+               diff_indent_heuristic = git_config_bool(var, value);
+               if (diff_indent_heuristic)
+                       diff_compaction_heuristic = 0;
+       }
+       if (!strcmp(var, "diff.compactionheuristic")) {
+               diff_compaction_heuristic = git_config_bool(var, value);
+               if (diff_compaction_heuristic)
+                       diff_indent_heuristic = 0;
+       }
+       return 0;
+}
+
 int git_diff_ui_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
@@ -190,10 +252,6 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
                diff_detect_rename_default = git_config_rename(var, value);
                return 0;
        }
-       if (!strcmp(var, "diff.compactionheuristic")) {
-               diff_compaction_heuristic = git_config_bool(var, value);
-               return 0;
-       }
        if (!strcmp(var, "diff.autorefreshindex")) {
                diff_auto_refresh_index = git_config_bool(var, value);
                return 0;
@@ -234,6 +292,17 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (git_diff_heuristic_config(var, value, cb) < 0)
+               return -1;
+
+       if (!strcmp(var, "diff.wserrorhighlight")) {
+               int val = parse_ws_error_highlight(value);
+               if (val < 0)
+                       return -1;
+               ws_error_highlight_default = val;
+               return 0;
+       }
+
        if (git_color_config(var, value, cb) < 0)
                return -1;
 
@@ -1616,7 +1685,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
         */
 
        if (options->stat_width == -1)
-               width = term_columns() - options->output_prefix_length;
+               width = term_columns() - strlen(line_prefix);
        else
                width = options->stat_width ? options->stat_width : 80;
        number_width = decimal_width(max_change) > number_width ?
@@ -1997,7 +2066,7 @@ static void show_dirstat(struct diff_options *options)
                return;
 
        /* Show all directories with more than x% of the changes */
-       qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
+       QSORT(dir.files, dir.nr, dirstat_compare);
        gather_dirstat(options, &dir, changed, "", 0);
 }
 
@@ -2041,7 +2110,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o
                return;
 
        /* Show all directories with more than x% of the changes */
-       qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
+       QSORT(dir.files, dir.nr, dirstat_compare);
        gather_dirstat(options, &dir, changed, "", 0);
 }
 
@@ -2290,17 +2359,37 @@ static void builtin_diff(const char *name_a,
        struct strbuf header = STRBUF_INIT;
        const char *line_prefix = diff_line_prefix(o);
 
-       if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
-                       (!one->mode || S_ISGITLINK(one->mode)) &&
-                       (!two->mode || S_ISGITLINK(two->mode))) {
+       diff_set_mnemonic_prefix(o, "a/", "b/");
+       if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
+               a_prefix = o->b_prefix;
+               b_prefix = o->a_prefix;
+       } else {
+               a_prefix = o->a_prefix;
+               b_prefix = o->b_prefix;
+       }
+
+       if (o->submodule_format == DIFF_SUBMODULE_LOG &&
+           (!one->mode || S_ISGITLINK(one->mode)) &&
+           (!two->mode || S_ISGITLINK(two->mode))) {
                const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
                show_submodule_summary(o->file, one->path ? one->path : two->path,
                                line_prefix,
-                               one->oid.hash, two->oid.hash,
+                               &one->oid, &two->oid,
                                two->dirty_submodule,
                                meta, del, add, reset);
                return;
+       } else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF &&
+                  (!one->mode || S_ISGITLINK(one->mode)) &&
+                  (!two->mode || S_ISGITLINK(two->mode))) {
+               const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
+               const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
+               show_submodule_inline_diff(o->file, one->path ? one->path : two->path,
+                               line_prefix,
+                               &one->oid, &two->oid,
+                               two->dirty_submodule,
+                               meta, del, add, reset, o);
+               return;
        }
 
        if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
@@ -2308,15 +2397,6 @@ static void builtin_diff(const char *name_a,
                textconv_two = get_textconv(two);
        }
 
-       diff_set_mnemonic_prefix(o, "a/", "b/");
-       if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
-               a_prefix = o->b_prefix;
-               b_prefix = o->a_prefix;
-       } else {
-               a_prefix = o->a_prefix;
-               b_prefix = o->b_prefix;
-       }
-
        /* Never use a non-valid filename anywhere if at all possible */
        name_a = DIFF_FILE_VALID(one) ? name_a : name_b;
        name_b = DIFF_FILE_VALID(two) ? name_b : name_a;
@@ -2690,7 +2770,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
         * This is not the sha1 we are looking for, or
         * unreusable because it is not a regular file.
         */
-       if (hashcmp(sha1, ce->sha1) || !S_ISREG(ce->ce_mode))
+       if (hashcmp(sha1, ce->oid.hash) || !S_ISREG(ce->ce_mode))
                return 0;
 
        /*
@@ -3016,6 +3096,21 @@ static int similarity_index(struct diff_filepair *p)
        return p->score * 100 / MAX_SCORE;
 }
 
+static const char *diff_abbrev_oid(const struct object_id *oid, int abbrev)
+{
+       if (startup_info->have_repository)
+               return find_unique_abbrev(oid->hash, abbrev);
+       else {
+               char *hex = oid_to_hex(oid);
+               if (abbrev < 0)
+                       abbrev = FALLBACK_DEFAULT_ABBREV;
+               if (abbrev > GIT_SHA1_HEXSZ)
+                       die("BUG: oid abbreviation out of range: %d", abbrev);
+               hex[abbrev] = '\0';
+               return hex;
+       }
+}
+
 static void fill_metainfo(struct strbuf *msg,
                          const char *name,
                          const char *other,
@@ -3074,9 +3169,9 @@ static void fill_metainfo(struct strbuf *msg,
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
                                abbrev = 40;
                }
-               strbuf_addf(msg, "%s%sindex %s..", line_prefix, set,
-                           find_unique_abbrev(one->oid.hash, abbrev));
-               strbuf_addstr(msg, find_unique_abbrev(two->oid.hash, abbrev));
+               strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set,
+                           diff_abbrev_oid(&one->oid, abbrev),
+                           diff_abbrev_oid(&two->oid, abbrev));
                if (one->mode == two->mode)
                        strbuf_addf(msg, " %06o", one->mode);
                strbuf_addf(msg, "%s\n", reset);
@@ -3274,7 +3369,7 @@ void diff_setup(struct diff_options *options)
        options->rename_limit = -1;
        options->dirstat_permille = diff_dirstat_permille_default;
        options->context = diff_context_default;
-       options->ws_error_highlight = WSEH_NEW;
+       options->ws_error_highlight = ws_error_highlight_default;
        DIFF_OPT_SET(options, RENAME_EMPTY);
 
        /* pathchange left =NULL by default */
@@ -3283,7 +3378,9 @@ void diff_setup(struct diff_options *options)
        options->use_color = diff_use_color_default;
        options->detect_rename = diff_detect_rename_default;
        options->xdl_opts |= diff_algorithm;
-       if (diff_compaction_heuristic)
+       if (diff_indent_heuristic)
+               DIFF_XDL_SET(options, INDENT_HEURISTIC);
+       else if (diff_compaction_heuristic)
                DIFF_XDL_SET(options, COMPACTION_HEURISTIC);
 
        options->orderfile = diff_order_file_cfg;
@@ -3312,7 +3409,7 @@ void diff_setup_done(struct diff_options *options)
        if (options->output_format & DIFF_FORMAT_NO_OUTPUT)
                count++;
        if (count > 1)
-               die("--name-only, --name-status, --check and -s are mutually exclusive");
+               die(_("--name-only, --name-status, --check and -s are mutually exclusive"));
 
        /*
         * Most of the time we can say "there are changes"
@@ -3386,7 +3483,7 @@ void diff_setup_done(struct diff_options *options)
                         */
                        read_cache();
        }
-       if (options->abbrev <= 0 || 40 < options->abbrev)
+       if (40 < options->abbrev)
                options->abbrev = 40; /* full */
 
        /*
@@ -3508,7 +3605,7 @@ static int stat_opt(struct diff_options *options, const char **av)
                        if (*arg == '=')
                                width = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
-                               die("Option '--stat-width' requires a value");
+                               die_want_option("--stat-width");
                        else if (!*arg) {
                                width = strtoul(av[1], &end, 10);
                                argcount = 2;
@@ -3517,7 +3614,7 @@ static int stat_opt(struct diff_options *options, const char **av)
                        if (*arg == '=')
                                name_width = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
-                               die("Option '--stat-name-width' requires a value");
+                               die_want_option("--stat-name-width");
                        else if (!*arg) {
                                name_width = strtoul(av[1], &end, 10);
                                argcount = 2;
@@ -3526,7 +3623,7 @@ static int stat_opt(struct diff_options *options, const char **av)
                        if (*arg == '=')
                                graph_width = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
-                               die("Option '--stat-graph-width' requires a value");
+                               die_want_option("--stat-graph-width");
                        else if (!*arg) {
                                graph_width = strtoul(av[1], &end, 10);
                                argcount = 2;
@@ -3535,7 +3632,7 @@ static int stat_opt(struct diff_options *options, const char **av)
                        if (*arg == '=')
                                count = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
-                               die("Option '--stat-count' requires a value");
+                               die_want_option("--stat-count");
                        else if (!*arg) {
                                count = strtoul(av[1], &end, 10);
                                argcount = 2;
@@ -3663,40 +3760,14 @@ static void enable_patch_output(int *fmt) {
        *fmt |= DIFF_FORMAT_PATCH;
 }
 
-static int parse_one_token(const char **arg, const char *token)
+static int parse_ws_error_highlight_opt(struct diff_options *opt, const char *arg)
 {
-       const char *rest;
-       if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) {
-               *arg = rest;
-               return 1;
-       }
-       return 0;
-}
+       int val = parse_ws_error_highlight(arg);
 
-static int parse_ws_error_highlight(struct diff_options *opt, const char *arg)
-{
-       const char *orig_arg = arg;
-       unsigned val = 0;
-       while (*arg) {
-               if (parse_one_token(&arg, "none"))
-                       val = 0;
-               else if (parse_one_token(&arg, "default"))
-                       val = WSEH_NEW;
-               else if (parse_one_token(&arg, "all"))
-                       val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT;
-               else if (parse_one_token(&arg, "new"))
-                       val |= WSEH_NEW;
-               else if (parse_one_token(&arg, "old"))
-                       val |= WSEH_OLD;
-               else if (parse_one_token(&arg, "context"))
-                       val |= WSEH_CONTEXT;
-               else {
-                       error("unknown value after ws-error-highlight=%.*s",
-                             (int)(arg - orig_arg), orig_arg);
-                       return 0;
-               }
-               if (*arg)
-                       arg++;
+       if (val < 0) {
+               error("unknown value after ws-error-highlight=%.*s",
+                     -1 - val, arg);
+               return 0;
        }
        opt->ws_error_highlight = val;
        return 1;
@@ -3805,9 +3876,15 @@ int diff_opt_parse(struct diff_options *options,
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
        else if (!strcmp(arg, "--ignore-blank-lines"))
                DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
-       else if (!strcmp(arg, "--compaction-heuristic"))
+       else if (!strcmp(arg, "--indent-heuristic")) {
+               DIFF_XDL_SET(options, INDENT_HEURISTIC);
+               DIFF_XDL_CLR(options, COMPACTION_HEURISTIC);
+       } else if (!strcmp(arg, "--no-indent-heuristic"))
+               DIFF_XDL_CLR(options, INDENT_HEURISTIC);
+       else if (!strcmp(arg, "--compaction-heuristic")) {
                DIFF_XDL_SET(options, COMPACTION_HEURISTIC);
-       else if (!strcmp(arg, "--no-compaction-heuristic"))
+               DIFF_XDL_CLR(options, INDENT_HEURISTIC);
+       } else if (!strcmp(arg, "--no-compaction-heuristic"))
                DIFF_XDL_CLR(options, COMPACTION_HEURISTIC);
        else if (!strcmp(arg, "--patience"))
                options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
@@ -3905,11 +3982,15 @@ int diff_opt_parse(struct diff_options *options,
                DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
                handle_ignore_submodules_arg(options, arg);
        } else if (!strcmp(arg, "--submodule"))
-               DIFF_OPT_SET(options, SUBMODULE_LOG);
+               options->submodule_format = DIFF_SUBMODULE_LOG;
        else if (skip_prefix(arg, "--submodule=", &arg))
                return parse_submodule_opt(options, arg);
        else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
-               return parse_ws_error_highlight(options, arg);
+               return parse_ws_error_highlight_opt(options, arg);
+       else if (!strcmp(arg, "--ita-invisible-in-index"))
+               options->ita_invisible_in_index = 1;
+       else if (!strcmp(arg, "--ita-visible-in-index"))
+               options->ita_invisible_in_index = 0;
 
        /* misc options */
        else if (!strcmp(arg, "-z"))
@@ -3956,6 +4037,12 @@ int diff_opt_parse(struct diff_options *options,
                options->a_prefix = optarg;
                return argcount;
        }
+       else if ((argcount = parse_long_opt("line-prefix", av, &optarg))) {
+               options->line_prefix = optarg;
+               options->line_prefix_length = strlen(options->line_prefix);
+               graph_setup_line_prefix(options);
+               return argcount;
+       }
        else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) {
                options->b_prefix = optarg;
                return argcount;
@@ -4089,27 +4176,46 @@ void diff_free_filepair(struct diff_filepair *p)
        free(p);
 }
 
-/* This is different from find_unique_abbrev() in that
- * it stuffs the result with dots for alignment.
- */
-const char *diff_unique_abbrev(const unsigned char *sha1, int len)
+const char *diff_aligned_abbrev(const struct object_id *oid, int len)
 {
        int abblen;
        const char *abbrev;
-       if (len == 40)
-               return sha1_to_hex(sha1);
 
-       abbrev = find_unique_abbrev(sha1, len);
+       if (len == GIT_SHA1_HEXSZ)
+               return oid_to_hex(oid);
+
+       abbrev = diff_abbrev_oid(oid, len);
        abblen = strlen(abbrev);
-       if (abblen < 37) {
-               static char hex[41];
+
+       /*
+        * In well-behaved cases, where the abbbreviated result is the
+        * same as the requested length, append three dots after the
+        * abbreviation (hence the whole logic is limited to the case
+        * where abblen < 37); when the actual abbreviated result is a
+        * bit longer than the requested length, we reduce the number
+        * of dots so that they match the well-behaved ones.  However,
+        * if the actual abbreviation is longer than the requested
+        * length by more than three, we give up on aligning, and add
+        * three dots anyway, to indicate that the output is not the
+        * full object name.  Yes, this may be suboptimal, but this
+        * appears only in "diff --raw --abbrev" output and it is not
+        * worth the effort to change it now.  Note that this would
+        * likely to work fine when the automatic sizing of default
+        * abbreviation length is used--we would be fed -1 in "len" in
+        * that case, and will end up always appending three-dots, but
+        * the automatic sizing is supposed to give abblen that ensures
+        * uniqueness across all objects (statistically speaking).
+        */
+       if (abblen < GIT_SHA1_HEXSZ - 3) {
+               static char hex[GIT_SHA1_HEXSZ + 1];
                if (len < abblen && abblen <= len + 2)
                        xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, "..");
                else
                        xsnprintf(hex, sizeof(hex), "%s...", abbrev);
                return hex;
        }
-       return sha1_to_hex(sha1);
+
+       return oid_to_hex(oid);
 }
 
 static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt)
@@ -4120,9 +4226,9 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt)
        fprintf(opt->file, "%s", diff_line_prefix(opt));
        if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) {
                fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode,
-                       diff_unique_abbrev(p->one->oid.hash, opt->abbrev));
+                       diff_aligned_abbrev(&p->one->oid, opt->abbrev));
                fprintf(opt->file, "%s ",
-                       diff_unique_abbrev(p->two->oid.hash, opt->abbrev));
+                       diff_aligned_abbrev(&p->two->oid, opt->abbrev));
        }
        if (p->score) {
                fprintf(opt->file, "%c%03d%c", p->status, similarity_index(p),
@@ -4591,25 +4697,25 @@ static int is_summary_empty(const struct diff_queue_struct *q)
 }
 
 static const char rename_limit_warning[] =
-"inexact rename detection was skipped due to too many files.";
+N_("inexact rename detection was skipped due to too many files.");
 
 static const char degrade_cc_to_c_warning[] =
-"only found copies from modified paths due to too many files.";
+N_("only found copies from modified paths due to too many files.");
 
 static const char rename_limit_advice[] =
-"you may want to set your %s variable to at least "
-"%d and retry the command.";
+N_("you may want to set your %s variable to at least "
+   "%d and retry the command.");
 
 void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
 {
        if (degraded_cc)
-               warning(degrade_cc_to_c_warning);
+               warning(_(degrade_cc_to_c_warning));
        else if (needed)
-               warning(rename_limit_warning);
+               warning(_(rename_limit_warning));
        else
                return;
        if (0 < needed && needed < 32767)
-               warning(rename_limit_advice, varname, needed);
+               warning(_(rename_limit_advice), varname, needed);
 }
 
 void diff_flush(struct diff_options *options)
@@ -4876,7 +4982,7 @@ static int diffnamecmp(const void *a_, const void *b_)
 void diffcore_fix_diff_index(struct diff_options *options)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
-       qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
+       QSORT(q->queue, q->nr, diffnamecmp);
 }
 
 void diffcore_std(struct diff_options *options)