Merge branch 'jc/1.7.0-diff-whitespace-only-status'
[gitweb.git] / diff.c
diff --git a/diff.c b/diff.c
index 0d7f5ea4a87d2152638f36b30d6366406d33bfa9..57aa72c6c575d1014cbb716d3993f600bdc1f2c5 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -39,6 +39,7 @@ static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_GREEN,        /* NEW */
        GIT_COLOR_YELLOW,       /* COMMIT */
        GIT_COLOR_BG_RED,       /* WHITESPACE */
+       GIT_COLOR_NORMAL,       /* FUNCINFO */
 };
 
 static void diff_filespec_load_driver(struct diff_filespec *one);
@@ -60,7 +61,9 @@ static int parse_diff_color_slot(const char *var, int ofs)
                return DIFF_COMMIT;
        if (!strcasecmp(var+ofs, "whitespace"))
                return DIFF_WHITESPACE;
-       die("bad config variable '%s'", var);
+       if (!strcasecmp(var+ofs, "func"))
+               return DIFF_FUNCINFO;
+       return -1;
 }
 
 static int git_config_rename(const char *var, const char *value)
@@ -119,6 +122,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
 
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
+               if (slot < 0)
+                       return 0;
                if (!value)
                        return config_error_nonbool(var);
                color_parse(value, var, diff_colors[slot]);
@@ -295,12 +300,13 @@ static void emit_line_0(FILE *file, const char *set, const char *reset,
                nofirst = 0;
        }
 
-       fputs(set, file);
-
-       if (!nofirst)
-               fputc(first, file);
-       fwrite(line, len, 1, file);
-       fputs(reset, file);
+       if (len || !nofirst) {
+               fputs(set, file);
+               if (!nofirst)
+                       fputc(first, file);
+               fwrite(line, len, 1, file);
+               fputs(reset, file);
+       }
        if (has_trailing_carriage_return)
                fputc('\r', file);
        if (has_trailing_newline)
@@ -344,6 +350,42 @@ static void emit_add_line(const char *reset,
        }
 }
 
+static void emit_hunk_header(struct emit_callback *ecbdata,
+                            const char *line, int len)
+{
+       const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
+       const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+       const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
+       const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+       static const char atat[2] = { '@', '@' };
+       const char *cp, *ep;
+
+       /*
+        * As a hunk header must begin with "@@ -<old>, +<new> @@",
+        * it always is at least 10 bytes long.
+        */
+       if (len < 10 ||
+           memcmp(line, atat, 2) ||
+           !(ep = memmem(line + 2, len - 2, atat, 2))) {
+               emit_line(ecbdata->file, plain, reset, line, len);
+               return;
+       }
+       ep += 2; /* skip over @@ */
+
+       /* The hunk header in fraginfo color */
+       emit_line(ecbdata->file, frag, reset, line, ep - line);
+
+       /* blank before the func header */
+       for (cp = ep; ep - line < len; ep++)
+               if (*ep != ' ' && *ep != '\t')
+                       break;
+       if (ep != cp)
+               emit_line(ecbdata->file, plain, reset, cp, ep - cp);
+
+       if (ep < line + len)
+               emit_line(ecbdata->file, func, reset, ep, line + len - ep);
+}
+
 static struct diff_tempfile *claim_diff_tempfile(void) {
        int i;
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
@@ -781,9 +823,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                        diff_words_flush(ecbdata);
                len = sane_truncate_line(ecbdata, line, len);
                find_lno(line, ecbdata);
-               emit_line(ecbdata->file,
-                         diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
-                         reset, line, len);
+               emit_hunk_header(ecbdata, line, len);
                if (line[len-1] != '\n')
                        putc('\n', ecbdata->file);
                return;
@@ -2511,6 +2551,20 @@ int diff_setup_done(struct diff_options *options)
        if (count > 1)
                die("--name-only, --name-status, --check and -s are mutually exclusive");
 
+       /*
+        * Most of the time we can say "there are changes"
+        * only by checking if there are changed paths, but
+        * --ignore-whitespace* options force us to look
+        * inside contents.
+        */
+
+       if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
+               DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
+       else
+               DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
+
        if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
                options->detect_rename = DIFF_DETECT_COPY;
 
@@ -2571,7 +2625,7 @@ int diff_setup_done(struct diff_options *options)
         * to have found.  It does not make sense not to return with
         * exit code in such a case either.
         */
-       if (DIFF_OPT_TST(options, QUIET)) {
+       if (DIFF_OPT_TST(options, QUICK)) {
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
@@ -2762,7 +2816,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        else if (!strcmp(arg, "--quiet"))
-               DIFF_OPT_SET(options, QUIET);
+               DIFF_OPT_SET(options, QUICK);
        else if (!strcmp(arg, "--ext-diff"))
                DIFF_OPT_SET(options, ALLOW_EXTERNAL);
        else if (!strcmp(arg, "--no-ext-diff"))
@@ -3469,6 +3523,18 @@ void diff_flush(struct diff_options *options)
        q->nr = q->alloc = 0;
        if (options->close_file)
                fclose(options->file);
+
+       /*
+        * Report the content-level differences with HAS_CHANGES;
+        * diff_addremove/diff_change does not set the bit when
+        * DIFF_FROM_CONTENTS is in effect (e.g. with -w).
+        */
+       if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+               if (options->found_changes)
+                       DIFF_OPT_SET(options, HAS_CHANGES);
+               else
+                       DIFF_OPT_CLR(options, HAS_CHANGES);
+       }
 }
 
 static void diffcore_apply_filter(const char *filter)
@@ -3605,7 +3671,7 @@ void diffcore_std(struct diff_options *options)
        diff_resolve_rename_copy();
        diffcore_apply_filter(options->filter);
 
-       if (diff_queued_diff.nr)
+       if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
                DIFF_OPT_SET(options, HAS_CHANGES);
        else
                DIFF_OPT_CLR(options, HAS_CHANGES);
@@ -3665,7 +3731,8 @@ void diff_addremove(struct diff_options *options,
                fill_filespec(two, sha1, mode);
 
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
 void diff_change(struct diff_options *options,
@@ -3697,7 +3764,8 @@ void diff_change(struct diff_options *options,
        fill_filespec(two, new_sha1, new_mode);
 
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
 void diff_unmerge(struct diff_options *options,