Merge branch 'pb/config' into next
[gitweb.git] / diff.c
diff --git a/diff.c b/diff.c
index e16e0bfc0a8999477f2c1d68a6b82cbc3867e8e5..bc32a4aa29171af6ca268a54cde881934079de83 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -25,6 +25,20 @@ int git_diff_config(const char *var, const char *value)
        return git_default_config(var, value);
 }
 
+enum color_diff {
+       DIFF_PLAIN = 0,
+       DIFF_METAINFO = 1,
+       DIFF_FILE_OLD = 2,
+       DIFF_FILE_NEW = 3,
+};
+
+static const char *diff_colors[] = {
+       "\033[0;0m",
+       "\033[1;35m",
+       "\033[1;31m",
+       "\033[1;34m",
+};
+
 static char *quote_one(const char *str)
 {
        int needlen;
@@ -177,23 +191,54 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
 }
 
 struct emit_callback {
+       struct xdiff_emit_state xm;
+       int nparents, color_diff;
        const char **label_path;
 };
 
-static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
+static inline void color_diff(int diff_use_color, enum color_diff ix)
+{
+       if (diff_use_color)
+               fputs(diff_colors[ix], stdout);
+}
+
+static void fn_out_consume(void *priv, char *line, unsigned long len)
 {
        int i;
        struct emit_callback *ecbdata = priv;
 
        if (ecbdata->label_path[0]) {
+               color_diff(ecbdata->color_diff, DIFF_METAINFO);
                printf("--- %s\n", ecbdata->label_path[0]);
+               color_diff(ecbdata->color_diff, DIFF_METAINFO);
                printf("+++ %s\n", ecbdata->label_path[1]);
                ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
        }
-       for (i = 0; i < nbuf; i++)
-               if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout))
-                       return -1;
-       return 0;
+
+       /* This is not really necessary for now because
+        * this codepath only deals with two-way diffs.
+        */
+       for (i = 0; i < len && line[i] == '@'; i++)
+               ;
+       if (2 <= i && i < len && line[i] == ' ') {
+               ecbdata->nparents = i - 1;
+               color_diff(ecbdata->color_diff, DIFF_METAINFO);
+       }
+       else if (len < ecbdata->nparents)
+               color_diff(ecbdata->color_diff, DIFF_PLAIN);
+       else {
+               int nparents = ecbdata->nparents;
+               int color = DIFF_PLAIN;
+               for (i = 0; i < nparents && len; i++) {
+                       if (line[i] == '-')
+                               color = DIFF_FILE_OLD;
+                       else if (line[i] == '+')
+                               color = DIFF_FILE_NEW;
+               }
+               color_diff(ecbdata->color_diff, color);
+       }
+       fwrite(line, len, 1, stdout);
+       color_diff(ecbdata->color_diff, DIFF_PLAIN);
 }
 
 static char *pprint_rename(const char *a, const char *b)
@@ -237,7 +282,7 @@ static char *pprint_rename(const char *a, const char *b)
                if (a_midlen < 0) a_midlen = 0;
                if (b_midlen < 0) b_midlen = 0;
 
-               name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7);
+               name = xmalloc(pfx_length + a_midlen + b_midlen + sfx_length + 7);
                sprintf(name, "%.*s{%.*s => %.*s}%s",
                        pfx_length, a,
                        a_midlen, a + pfx_length,
@@ -299,6 +344,7 @@ static void diffstat_consume(void *priv, char *line, unsigned long len)
 
 static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
 static const char minuses[]= "----------------------------------------------------------------------";
+const char mime_boundary_leader[] = "------------";
 
 static void show_stats(struct diffstat_t* data)
 {
@@ -397,6 +443,46 @@ static void show_stats(struct diffstat_t* data)
                        total_files, adds, dels);
 }
 
+struct checkdiff_t {
+       struct xdiff_emit_state xm;
+       const char *filename;
+       int lineno;
+};
+
+static void checkdiff_consume(void *priv, char *line, unsigned long len)
+{
+       struct checkdiff_t *data = priv;
+
+       if (line[0] == '+') {
+               int i, spaces = 0;
+
+               data->lineno++;
+
+               /* check space before tab */
+               for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++)
+                       if (line[i] == ' ')
+                               spaces++;
+               if (line[i - 1] == '\t' && spaces)
+                       printf("%s:%d: space before tab:%.*s\n",
+                               data->filename, data->lineno, (int)len, line);
+
+               /* check white space at line end */
+               if (line[len - 1] == '\n')
+                       len--;
+               if (isspace(line[len - 1]))
+                       printf("%s:%d: white space at end: %.*s\n",
+                               data->filename, data->lineno, (int)len, line);
+       } else if (line[0] == ' ')
+               data->lineno++;
+       else if (line[0] == '@') {
+               char *plus = strchr(line, '+');
+               if (plus)
+                       data->lineno = strtol(plus, NULL, 10);
+               else
+                       die("invalid diff");
+       }
+}
+
 static unsigned char *deflate_it(char *data,
                                 unsigned long size,
                                 unsigned long *result_size)
@@ -508,25 +594,35 @@ static void builtin_diff(const char *name_a,
        b_two = quote_two("b/", name_b);
        lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
        lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
+       color_diff(o->color_diff, DIFF_METAINFO);
        printf("diff --git %s %s\n", a_one, b_two);
        if (lbl[0][0] == '/') {
                /* /dev/null */
+               color_diff(o->color_diff, DIFF_METAINFO);
                printf("new file mode %06o\n", two->mode);
-               if (xfrm_msg && xfrm_msg[0])
+               if (xfrm_msg && xfrm_msg[0]) {
+                       color_diff(o->color_diff, DIFF_METAINFO);
                        puts(xfrm_msg);
+               }
        }
        else if (lbl[1][0] == '/') {
                printf("deleted file mode %06o\n", one->mode);
-               if (xfrm_msg && xfrm_msg[0])
+               if (xfrm_msg && xfrm_msg[0]) {
+                       color_diff(o->color_diff, DIFF_METAINFO);
                        puts(xfrm_msg);
+               }
        }
        else {
                if (one->mode != two->mode) {
+                       color_diff(o->color_diff, DIFF_METAINFO);
                        printf("old mode %06o\n", one->mode);
+                       color_diff(o->color_diff, DIFF_METAINFO);
                        printf("new mode %06o\n", two->mode);
                }
-               if (xfrm_msg && xfrm_msg[0])
+               if (xfrm_msg && xfrm_msg[0]) {
+                       color_diff(o->color_diff, DIFF_METAINFO);
                        puts(xfrm_msg);
+               }
                /*
                 * we do not run diff between different kind
                 * of objects.
@@ -534,6 +630,7 @@ static void builtin_diff(const char *name_a,
                if ((one->mode ^ two->mode) & S_IFMT)
                        goto free_ab_and_return;
                if (complete_rewrite) {
+                       color_diff(o->color_diff, DIFF_PLAIN);
                        emit_rewrite_diff(name_a, name_b, one, two);
                        goto free_ab_and_return;
                }
@@ -561,7 +658,9 @@ static void builtin_diff(const char *name_a,
                xdemitcb_t ecb;
                struct emit_callback ecbdata;
 
+               memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
+               ecbdata.color_diff = o->color_diff;
                xpp.flags = XDF_NEED_MINIMAL;
                xecfg.ctxlen = o->context;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
@@ -571,8 +670,9 @@ static void builtin_diff(const char *name_a,
                        xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
                else if (!strncmp(diffopts, "-u", 2))
                        xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
-               ecb.outf = fn_out;
+               ecb.outf = xdiff_outf;
                ecb.priv = &ecbdata;
+               ecbdata.xm.consume = fn_out_consume;
                xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
        }
 
@@ -624,6 +724,41 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
        }
 }
 
+static void builtin_checkdiff(const char *name_a, const char *name_b,
+                            struct diff_filespec *one,
+                            struct diff_filespec *two)
+{
+       mmfile_t mf1, mf2;
+       struct checkdiff_t data;
+
+       if (!two)
+               return;
+
+       memset(&data, 0, sizeof(data));
+       data.xm.consume = checkdiff_consume;
+       data.filename = name_b ? name_b : name_a;
+       data.lineno = 0;
+
+       if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+               die("unable to read files to diff");
+
+       if (mmfile_is_binary(&mf2))
+               return;
+       else {
+               /* Crazy xdl interfaces.. */
+               xpparam_t xpp;
+               xdemitconf_t xecfg;
+               xdemitcb_t ecb;
+
+               xpp.flags = XDF_NEED_MINIMAL;
+               xecfg.ctxlen = 0;
+               xecfg.flags = 0;
+               ecb.outf = xdiff_outf;
+               ecb.priv = &data;
+               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+       }
+}
+
 struct diff_filespec *alloc_filespec(const char *path)
 {
        int namelen = strlen(path);
@@ -1180,6 +1315,25 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
        builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite);
 }
 
+static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
+{
+       const char *name;
+       const char *other;
+
+       if (DIFF_PAIR_UNMERGED(p)) {
+               /* unmerged */
+               return;
+       }
+
+       name = p->one->path;
+       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+
+       diff_fill_sha1_info(p->one);
+       diff_fill_sha1_info(p->two);
+
+       builtin_checkdiff(name, other, p->one, p->two);
+}
+
 void diff_setup(struct diff_options *options)
 {
        memset(options, 0, sizeof(*options));
@@ -1205,9 +1359,18 @@ int diff_setup_done(struct diff_options *options)
         * recursive bits for other formats here.
         */
        if ((options->output_format == DIFF_FORMAT_PATCH) ||
-           (options->output_format == DIFF_FORMAT_DIFFSTAT))
+           (options->output_format == DIFF_FORMAT_DIFFSTAT) ||
+           (options->output_format == DIFF_FORMAT_CHECKDIFF))
                options->recursive = 1;
 
+       /*
+        * These combinations do not make sense.
+        */
+       if (options->output_format == DIFF_FORMAT_RAW)
+               options->with_raw = 0;
+       if (options->output_format == DIFF_FORMAT_DIFFSTAT)
+               options->with_stat  = 0;
+
        if (options->detect_rename && options->rename_limit < 0)
                options->rename_limit = diff_rename_limit_default;
        if (options->setup & DIFF_SETUP_USE_CACHE) {
@@ -1288,6 +1451,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        }
        else if (!strcmp(arg, "--stat"))
                options->output_format = DIFF_FORMAT_DIFFSTAT;
+       else if (!strcmp(arg, "--check"))
+               options->output_format = DIFF_FORMAT_CHECKDIFF;
        else if (!strcmp(arg, "--summary"))
                options->summary = 1;
        else if (!strcmp(arg, "--patch-with-stat")) {
@@ -1350,6 +1515,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                else if (40 < options->abbrev)
                        options->abbrev = 40;
        }
+       else if (!strcmp(arg, "--color"))
+               options->color_diff = 1;
        else
                return 0;
        return 1;
@@ -1610,6 +1777,19 @@ static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
        run_diffstat(p, o, diffstat);
 }
 
+static void diff_flush_checkdiff(struct diff_filepair *p,
+               struct diff_options *o)
+{
+       if (diff_unmodified_pair(p))
+               return;
+
+       if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+           (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+               return; /* no tree diffs in patch format */
+
+       run_checkdiff(p, o);
+}
+
 int diff_queue_is_empty(void)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
@@ -1740,6 +1920,9 @@ static void flush_one_pair(struct diff_filepair *p,
                case DIFF_FORMAT_DIFFSTAT:
                        diff_flush_stat(p, options, diffstat);
                        break;
+               case DIFF_FORMAT_CHECKDIFF:
+                       diff_flush_checkdiff(p, options);
+                       break;
                case DIFF_FORMAT_PATCH:
                        diff_flush_patch(p, options);
                        break;
@@ -1867,7 +2050,13 @@ void diff_flush(struct diff_options *options)
                show_stats(diffstat);
                free(diffstat);
                diffstat = NULL;
-               putchar(options->line_termination);
+               if (options->summary)
+                       for (i = 0; i < q->nr; i++)
+                               diff_summary(q->queue[i]);
+               if (options->stat_sep)
+                       fputs(options->stat_sep, stdout);
+               else
+                       putchar(options->line_termination);
        }
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
@@ -1880,7 +2069,7 @@ void diff_flush(struct diff_options *options)
        }
 
        for (i = 0; i < q->nr; i++) {
-               if (options->summary)
+               if (diffstat && options->summary)
                        diff_summary(q->queue[i]);
                diff_free_filepair(q->queue[i]);
        }