Code

Merge branch 'pb/config' into next
[git.git] / diff.c
diff --git a/diff.c b/diff.c
index 7a7b839e56ac1ba2ed0b770a047aaf07bce23722..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)
@@ -232,11 +277,16 @@ static char *pprint_rename(const char *a, const char *b)
         * name-a => name-b
         */
        if (pfx_length + sfx_length) {
-               name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7);
+               int a_midlen = len_a - pfx_length - sfx_length;
+               int b_midlen = len_b - pfx_length - sfx_length;
+               if (a_midlen < 0) a_midlen = 0;
+               if (b_midlen < 0) b_midlen = 0;
+
+               name = xmalloc(pfx_length + a_midlen + b_midlen + sfx_length + 7);
                sprintf(name, "%.*s{%.*s => %.*s}%s",
                        pfx_length, a,
-                       len_a - pfx_length - sfx_length, a + pfx_length,
-                       len_b - pfx_length - sfx_length, b + pfx_length,
+                       a_midlen, a + pfx_length,
+                       b_midlen, b + pfx_length,
                        a + len_a - sfx_length);
        }
        else {
@@ -294,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)
 {
@@ -392,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)
@@ -503,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.
@@ -529,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;
                }
@@ -556,9 +658,11 @@ 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 = 3;
+               xecfg.ctxlen = o->context;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
                if (!diffopts)
                        ;
@@ -566,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);
        }
 
@@ -619,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);
@@ -1175,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));
@@ -1182,6 +1341,7 @@ void diff_setup(struct diff_options *options)
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
+       options->context = 3;
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
@@ -1199,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) {
@@ -1222,17 +1391,70 @@ int diff_setup_done(struct diff_options *options)
        return 0;
 }
 
+int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
+{
+       char c, *eq;
+       int len;
+
+       if (*arg != '-')
+               return 0;
+       c = *++arg;
+       if (!c)
+               return 0;
+       if (c == arg_short) {
+               c = *++arg;
+               if (!c)
+                       return 1;
+               if (val && isdigit(c)) {
+                       char *end;
+                       int n = strtoul(arg, &end, 10);
+                       if (*end)
+                               return 0;
+                       *val = n;
+                       return 1;
+               }
+               return 0;
+       }
+       if (c != '-')
+               return 0;
+       arg++;
+       eq = strchr(arg, '=');
+       if (eq)
+               len = eq - arg;
+       else
+               len = strlen(arg);
+       if (!len || strncmp(arg, arg_long, len))
+               return 0;
+       if (eq) {
+               int n;
+               char *end;
+               if (!isdigit(*++eq))
+                       return 0;
+               n = strtoul(eq, &end, 10);
+               if (*end)
+                       return 0;
+               *val = n;
+       }
+       return 1;
+}
+
 int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 {
        const char *arg = av[0];
        if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
                options->output_format = DIFF_FORMAT_PATCH;
+       else if (opt_arg(arg, 'U', "unified", &options->context))
+               options->output_format = DIFF_FORMAT_PATCH;
        else if (!strcmp(arg, "--patch-with-raw")) {
                options->output_format = DIFF_FORMAT_PATCH;
                options->with_raw = 1;
        }
        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")) {
                options->output_format = DIFF_FORMAT_PATCH;
                options->with_stat = 1;
@@ -1293,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;
@@ -1553,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;
@@ -1683,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;
@@ -1703,6 +1943,85 @@ static void flush_one_pair(struct diff_filepair *p,
        }
 }
 
+static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
+{
+       if (fs->mode)
+               printf(" %s mode %06o %s\n", newdelete, fs->mode, fs->path);
+       else
+               printf(" %s %s\n", newdelete, fs->path);
+}
+
+
+static void show_mode_change(struct diff_filepair *p, int show_name)
+{
+       if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) {
+               if (show_name)
+                       printf(" mode change %06o => %06o %s\n",
+                              p->one->mode, p->two->mode, p->two->path);
+               else
+                       printf(" mode change %06o => %06o\n",
+                              p->one->mode, p->two->mode);
+       }
+}
+
+static void show_rename_copy(const char *renamecopy, struct diff_filepair *p)
+{
+       const char *old, *new;
+
+       /* Find common prefix */
+       old = p->one->path;
+       new = p->two->path;
+       while (1) {
+               const char *slash_old, *slash_new;
+               slash_old = strchr(old, '/');
+               slash_new = strchr(new, '/');
+               if (!slash_old ||
+                   !slash_new ||
+                   slash_old - old != slash_new - new ||
+                   memcmp(old, new, slash_new - new))
+                       break;
+               old = slash_old + 1;
+               new = slash_new + 1;
+       }
+       /* p->one->path thru old is the common prefix, and old and new
+        * through the end of names are renames
+        */
+       if (old != p->one->path)
+               printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
+                      (int)(old - p->one->path), p->one->path,
+                      old, new, (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       else
+               printf(" %s %s => %s (%d%%)\n", renamecopy,
+                      p->one->path, p->two->path,
+                      (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       show_mode_change(p, 0);
+}
+
+static void diff_summary(struct diff_filepair *p)
+{
+       switch(p->status) {
+       case DIFF_STATUS_DELETED:
+               show_file_mode_name("delete", p->one);
+               break;
+       case DIFF_STATUS_ADDED:
+               show_file_mode_name("create", p->two);
+               break;
+       case DIFF_STATUS_COPIED:
+               show_rename_copy("copy", p);
+               break;
+       case DIFF_STATUS_RENAMED:
+               show_rename_copy("rename", p);
+               break;
+       default:
+               if (p->score) {
+                       printf(" rewrite %s (%d%%)\n", p->two->path,
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE));
+                       show_mode_change(p, 0);
+               } else  show_mode_change(p, 1);
+               break;
+       }
+}
+
 void diff_flush(struct diff_options *options)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
@@ -1731,12 +2050,17 @@ 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];
                flush_one_pair(p, diff_output_format, options, diffstat);
-               diff_free_filepair(p);
        }
 
        if (diffstat) {
@@ -1744,6 +2068,12 @@ void diff_flush(struct diff_options *options)
                free(diffstat);
        }
 
+       for (i = 0; i < q->nr; i++) {
+               if (diffstat && options->summary)
+                       diff_summary(q->queue[i]);
+               diff_free_filepair(q->queue[i]);
+       }
+
        free(q->queue);
        q->queue = NULL;
        q->nr = q->alloc = 0;