Code

bundle, fast-import: detect write failure
[git.git] / diff.c
diff --git a/diff.c b/diff.c
index 823707548422d37e63b1eeae18a25fa63e91b898..b18c140e3df7acc6e5bddd499d7ed7a4e672bf97 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -10,6 +10,7 @@
 #include "color.h"
 #include "attr.h"
 #include "run-command.h"
+#include "utf8.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -20,6 +21,7 @@
 static int diff_detect_rename_default;
 static int diff_rename_limit_default = 100;
 static int diff_use_color_default;
+static const char *external_diff_cmd_cfg;
 int diff_auto_refresh_index = 1;
 
 static char diff_colors[][COLOR_MAXLEN] = {
@@ -58,14 +60,6 @@ static struct ll_diff_driver {
        char *cmd;
 } *user_diff, **user_diff_tail;
 
-static void read_config_if_needed(void)
-{
-       if (!user_diff_tail) {
-               user_diff_tail = &user_diff;
-               git_config(git_diff_ui_config);
-       }
-}
-
 /*
  * Currently there is only "diff.<drivername>.command" variable;
  * because there are "diff.color.<slot>" variables, we are parsing
@@ -163,22 +157,38 @@ int git_diff_ui_config(const char *var, const char *value)
                diff_auto_refresh_index = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "diff.external")) {
+               external_diff_cmd_cfg = xstrdup(value);
+               return 0;
+       }
        if (!prefixcmp(var, "diff.")) {
                const char *ep = strrchr(var, '.');
 
                if (ep != var + 4) {
                        if (!strcmp(ep, ".command"))
                                return parse_lldiff_command(var, ep, value);
-                       if (!strcmp(ep, ".funcname"))
-                               return parse_funcname_pattern(var, ep, value);
                }
        }
+
+       return git_diff_basic_config(var, value);
+}
+
+int git_diff_basic_config(const char *var, const char *value)
+{
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
                color_parse(value, var, diff_colors[slot]);
                return 0;
        }
 
+       if (!prefixcmp(var, "diff.")) {
+               const char *ep = strrchr(var, '.');
+               if (ep != var + 4) {
+                       if (!strcmp(ep, ".funcname"))
+                               return parse_funcname_pattern(var, ep, value);
+               }
+       }
+
        return git_default_config(var, value);
 }
 
@@ -209,6 +219,8 @@ static const char *external_diff(void)
        if (done_preparing)
                return external_diff_cmd;
        external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
+       if (!external_diff_cmd)
+               external_diff_cmd = external_diff_cmd_cfg;
        done_preparing = 1;
        return external_diff_cmd;
 }
@@ -283,28 +295,35 @@ static void emit_rewrite_diff(const char *name_a,
                              const char *name_b,
                              struct diff_filespec *one,
                              struct diff_filespec *two,
-                             int color_diff)
+                             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 *old = diff_get_color(color_diff, DIFF_FILE_OLD);
        const char *new = diff_get_color(color_diff, DIFF_FILE_NEW);
        const char *reset = diff_get_color(color_diff, DIFF_RESET);
+       static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
 
        name_a += (*name_a == '/');
        name_b += (*name_b == '/');
        name_a_tab = strchr(name_a, ' ') ? "\t" : "";
        name_b_tab = strchr(name_b, ' ') ? "\t" : "";
 
+       strbuf_reset(&a_name);
+       strbuf_reset(&b_name);
+       quote_two_c_style(&a_name, o->a_prefix, name_a, 0);
+       quote_two_c_style(&b_name, o->b_prefix, name_b, 0);
+
        diff_populate_filespec(one, 0);
        diff_populate_filespec(two, 0);
        lc_a = count_lines(one->data, one->size);
        lc_b = count_lines(two->data, two->size);
-       printf("%s--- a/%s%s%s\n%s+++ b/%s%s%s\n%s@@ -",
-              metainfo, name_a, name_a_tab, reset,
-              metainfo, name_b, name_b_tab, reset, fraginfo);
+       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);
@@ -451,10 +470,13 @@ static void diff_words_show(struct diff_words_data *diff_words)
        }
 }
 
+typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
+
 struct emit_callback {
        struct xdiff_emit_state xm;
        int nparents, color_diff;
        unsigned ws_rule;
+       sane_truncate_fn truncate;
        const char **label_path;
        struct diff_words_data *diff_words;
        int *found_changesp;
@@ -486,88 +508,9 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix)
 
 static void emit_line(const char *set, const char *reset, const char *line, int len)
 {
-       if (len > 0 && line[len-1] == '\n')
-               len--;
        fputs(set, stdout);
        fwrite(line, len, 1, stdout);
-       puts(reset);
-}
-
-static void emit_line_with_ws(int nparents,
-                             const char *set, const char *reset, const char *ws,
-                             const char *line, int len, unsigned ws_rule)
-{
-       int col0 = nparents;
-       int last_tab_in_indent = -1;
-       int last_space_in_indent = -1;
-       int i;
-       int tail = len;
-       int need_highlight_leading_space = 0;
-       /*
-        * The line is a newly added line.  Does it have funny leading
-        * whitespaces?  In indent, SP should never precede a TAB.  In
-        * addition, under "indent with non tab" rule, there should not
-        * be more than 8 consecutive spaces.
-        */
-       for (i = col0; i < len; i++) {
-               if (line[i] == '\t') {
-                       last_tab_in_indent = i;
-                       if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
-                           0 <= last_space_in_indent)
-                               need_highlight_leading_space = 1;
-               }
-               else if (line[i] == ' ')
-                       last_space_in_indent = i;
-               else
-                       break;
-       }
-       if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
-           0 <= last_space_in_indent &&
-           last_tab_in_indent < 0 &&
-           8 <= (i - col0)) {
-               last_tab_in_indent = i;
-               need_highlight_leading_space = 1;
-       }
-       fputs(set, stdout);
-       fwrite(line, col0, 1, stdout);
        fputs(reset, stdout);
-       if (((i == len) || line[i] == '\n') && i != col0) {
-               /* The whole line was indent */
-               emit_line(ws, reset, line + col0, len - col0);
-               return;
-       }
-       i = col0;
-       if (need_highlight_leading_space) {
-               while (i < last_tab_in_indent) {
-                       if (line[i] == ' ') {
-                               fputs(ws, stdout);
-                               putchar(' ');
-                               fputs(reset, stdout);
-                       }
-                       else
-                               putchar(line[i]);
-                       i++;
-               }
-       }
-       tail = len - 1;
-       if (line[tail] == '\n' && i < tail)
-               tail--;
-       if (ws_rule & WS_TRAILING_SPACE) {
-               while (i < tail) {
-                       if (!isspace(line[tail]))
-                               break;
-                       tail--;
-               }
-       }
-       if ((i < tail && line[tail + 1] != '\n')) {
-               /* This has whitespace between tail+1..len */
-               fputs(set, stdout);
-               fwrite(line + i, tail - i + 1, 1, stdout);
-               fputs(reset, stdout);
-               emit_line(ws, reset, line + tail + 1, len - tail - 1);
-       }
-       else
-               emit_line(set, reset, line + i, len - i);
 }
 
 static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
@@ -577,9 +520,31 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons
 
        if (!*ws)
                emit_line(set, reset, line, len);
-       else
-               emit_line_with_ws(ecbdata->nparents, set, reset, ws,
-                                 line, len, ecbdata->ws_rule);
+       else {
+               /* Emit just the prefix, then the rest. */
+               emit_line(set, reset, line, ecbdata->nparents);
+               (void)check_and_emit_line(line + ecbdata->nparents,
+                   len - ecbdata->nparents, ecbdata->ws_rule,
+                   stdout, set, reset, ws);
+       }
+}
+
+static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
+{
+       const char *cp;
+       unsigned long allot;
+       size_t l = len;
+
+       if (ecb->truncate)
+               return ecb->truncate(line, len);
+       cp = line;
+       allot = l;
+       while (0 < l) {
+               (void) utf8_width(&cp, &l);
+               if (!cp)
+                       break; /* truncated in the middle? */
+       }
+       return allot - l;
 }
 
 static void fn_out_consume(void *priv, char *line, unsigned long len)
@@ -612,8 +577,11 @@ 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),
                          reset, line, len);
+               if (line[len-1] != '\n')
+                       putchar('\n');
                return;
        }
 
@@ -1040,45 +1008,19 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
        const char *ws = diff_get_color(data->color_diff, DIFF_WHITESPACE);
        const char *reset = diff_get_color(data->color_diff, DIFF_RESET);
        const char *set = diff_get_color(data->color_diff, DIFF_FILE_NEW);
+       char *err;
 
        if (line[0] == '+') {
-               int i, spaces = 0, space_before_tab = 0, white_space_at_end = 0;
-
-               /* check space before tab */
-               for (i = 1; i < len; i++) {
-                       if (line[i] == ' ')
-                               spaces++;
-                       else if (line[i] == '\t') {
-                               if (spaces) {
-                                       space_before_tab = 1;
-                                       break;
-                               }
-                       }
-                       else
-                               break;
-               }
-
-               /* check whitespace at line end */
-               if (line[len - 1] == '\n')
-                       len--;
-               if (isspace(line[len - 1]))
-                       white_space_at_end = 1;
-
-               if (space_before_tab || white_space_at_end) {
-                       data->status = 1;
-                       printf("%s:%d: %s", data->filename, data->lineno, ws);
-                       if (space_before_tab) {
-                               printf("space before tab");
-                               if (white_space_at_end)
-                                       putchar(',');
-                       }
-                       if (white_space_at_end)
-                               printf("whitespace at end");
-                       printf(":%s ", reset);
-                       emit_line_with_ws(1, set, reset, ws, line, len,
-                                         data->ws_rule);
-               }
-
+               data->status = check_and_emit_line(line + 1, len - 1,
+                   data->ws_rule, NULL, NULL, NULL, NULL);
+               if (!data->status)
+                       return;
+               err = whitespace_error_string(data->status);
+               printf("%s:%d: %s.\n", data->filename, data->lineno, err);
+               free(err);
+               emit_line(set, reset, line, 1);
+               (void)check_and_emit_line(line + 1, len - 1, data->ws_rule,
+                   stdout, set, reset, ws);
                data->lineno++;
        } else if (line[0] == ' ')
                data->lineno++;
@@ -1241,7 +1183,6 @@ static const char *funcname_pattern(const char *ident)
 {
        struct funcname_pattern *pp;
 
-       read_config_if_needed();
        for (pp = funcname_pattern_list; pp; pp = pp->next)
                if (!strcmp(ident, pp->name))
                        return pp->pattern;
@@ -1306,8 +1247,8 @@ static void builtin_diff(const char *name_a,
        const char *set = diff_get_color_opt(o, DIFF_METAINFO);
        const char *reset = diff_get_color_opt(o, DIFF_RESET);
 
-       a_one = quote_two("a/", name_a + (*name_a == '/'));
-       b_two = quote_two("b/", name_b + (*name_b == '/'));
+       a_one = quote_two(o->a_prefix, name_a + (*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);
@@ -1336,8 +1277,7 @@ static void builtin_diff(const char *name_a,
                if ((one->mode ^ two->mode) & S_IFMT)
                        goto free_ab_and_return;
                if (complete_rewrite) {
-                       emit_rewrite_diff(name_a, name_b, one, two,
-                                       DIFF_OPT_TST(o, COLOR_DIFF));
+                       emit_rewrite_diff(name_a, name_b, one, two, o);
                        o->found_changes = 1;
                        goto free_ab_and_return;
                }
@@ -1899,7 +1839,6 @@ static const char *external_diff_attr(const char *name)
                    !ATTR_UNSET(value)) {
                        struct ll_diff_driver *drv;
 
-                       read_config_if_needed();
                        for (drv = user_diff; drv; drv = drv->next)
                                if (!strcmp(drv->name, value))
                                        return drv->cmd;
@@ -2114,6 +2053,9 @@ void diff_setup(struct diff_options *options)
        else
                DIFF_OPT_CLR(options, COLOR_DIFF);
        options->detect_rename = diff_detect_rename_default;
+
+       options->a_prefix = "a/";
+       options->b_prefix = "b/";
 }
 
 int diff_setup_done(struct diff_options *options)
@@ -2125,12 +2067,7 @@ int diff_setup_done(struct diff_options *options)
        if (options->output_format & DIFF_FORMAT_NAME_STATUS)
                count++;
        if (options->output_format & DIFF_FORMAT_CHECKDIFF)
-       {
                count++;
-               if (DIFF_OPT_TST(options, QUIET) ||
-                   DIFF_OPT_TST(options, EXIT_WITH_STATUS))
-                       die("--check may not be used with --quiet or --exit-code");
-       }
        if (options->output_format & DIFF_FORMAT_NO_OUTPUT)
                count++;
        if (count > 1)
@@ -2390,6 +2327,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                else if (40 < options->abbrev)
                        options->abbrev = 40;
        }
+       else if (!prefixcmp(arg, "--src-prefix="))
+               options->a_prefix = arg + 13;
+       else if (!prefixcmp(arg, "--dst-prefix="))
+               options->b_prefix = arg + 13;
+       else if (!strcmp(arg, "--no-prefix"))
+               options->a_prefix = options->b_prefix = "";
        else
                return 0;
        return 1;
@@ -3180,6 +3123,20 @@ void diffcore_std(struct diff_options *options)
                DIFF_OPT_CLR(options, HAS_CHANGES);
 }
 
+int diff_result_code(struct diff_options *opt, int status)
+{
+       int result = 0;
+       if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
+           !(opt->output_format & DIFF_FORMAT_CHECKDIFF))
+               return status;
+       if (DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
+           DIFF_OPT_TST(opt, HAS_CHANGES))
+               result |= 01;
+       if ((opt->output_format & DIFF_FORMAT_CHECKDIFF) &&
+           DIFF_OPT_TST(opt, CHECK_FAILED))
+               result |= 02;
+       return result;
+}
 
 void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,