Fix some doubled word typos
[gitweb.git] / diff.c
diff --git a/diff.c b/diff.c
index a7bb9b95bd841a581d8d47d86b96b7140e9b8133..507e4019e8bc2764daaf31ce76112ab509895704 100644 (file)
--- a/diff.c
+++ b/diff.c
 
 static int use_size_cache;
 
-int diff_rename_limit_default = -1;
+static int diff_rename_limit_default = -1;
+static int diff_use_color_default = 0;
+
+enum color_diff {
+       DIFF_RESET = 0,
+       DIFF_PLAIN = 1,
+       DIFF_METAINFO = 2,
+       DIFF_FRAGINFO = 3,
+       DIFF_FILE_OLD = 4,
+       DIFF_FILE_NEW = 5,
+};
+
+#define COLOR_NORMAL  ""
+#define COLOR_BOLD    "\033[1m"
+#define COLOR_DIM     "\033[2m"
+#define COLOR_UL      "\033[4m"
+#define COLOR_BLINK   "\033[5m"
+#define COLOR_REVERSE "\033[7m"
+#define COLOR_RESET   "\033[m"
+
+#define COLOR_BLACK   "\033[30m"
+#define COLOR_RED     "\033[31m"
+#define COLOR_GREEN   "\033[32m"
+#define COLOR_YELLOW  "\033[33m"
+#define COLOR_BLUE    "\033[34m"
+#define COLOR_MAGENTA "\033[35m"
+#define COLOR_CYAN    "\033[36m"
+#define COLOR_WHITE   "\033[37m"
+
+static const char *diff_colors[] = {
+       [DIFF_RESET]    = COLOR_RESET,
+       [DIFF_PLAIN]    = COLOR_NORMAL,
+       [DIFF_METAINFO] = COLOR_BOLD,
+       [DIFF_FRAGINFO] = COLOR_CYAN,
+       [DIFF_FILE_OLD] = COLOR_RED,
+       [DIFF_FILE_NEW] = COLOR_GREEN,
+};
+
+static int parse_diff_color_slot(const char *var, int ofs)
+{
+       if (!strcasecmp(var+ofs, "plain"))
+               return DIFF_PLAIN;
+       if (!strcasecmp(var+ofs, "meta"))
+               return DIFF_METAINFO;
+       if (!strcasecmp(var+ofs, "frag"))
+               return DIFF_FRAGINFO;
+       if (!strcasecmp(var+ofs, "old"))
+               return DIFF_FILE_OLD;
+       if (!strcasecmp(var+ofs, "new"))
+               return DIFF_FILE_NEW;
+       die("bad config variable '%s'", var);
+}
+
+static const char *parse_diff_color_value(const char *value, const char *var)
+{
+       if (!strcasecmp(value, "normal"))
+               return COLOR_NORMAL;
+       if (!strcasecmp(value, "bold"))
+               return COLOR_BOLD;
+       if (!strcasecmp(value, "dim"))
+               return COLOR_DIM;
+       if (!strcasecmp(value, "ul"))
+               return COLOR_UL;
+       if (!strcasecmp(value, "blink"))
+               return COLOR_BLINK;
+       if (!strcasecmp(value, "reverse"))
+               return COLOR_REVERSE;
+       if (!strcasecmp(value, "reset"))
+               return COLOR_RESET;
+       if (!strcasecmp(value, "black"))
+               return COLOR_BLACK;
+       if (!strcasecmp(value, "red"))
+               return COLOR_RED;
+       if (!strcasecmp(value, "green"))
+               return COLOR_GREEN;
+       if (!strcasecmp(value, "yellow"))
+               return COLOR_YELLOW;
+       if (!strcasecmp(value, "blue"))
+               return COLOR_BLUE;
+       if (!strcasecmp(value, "magenta"))
+               return COLOR_MAGENTA;
+       if (!strcasecmp(value, "cyan"))
+               return COLOR_CYAN;
+       if (!strcasecmp(value, "white"))
+               return COLOR_WHITE;
+       die("bad config value '%s' for variable '%s'", value, var);
+}
 
 int git_diff_config(const char *var, const char *value)
 {
@@ -21,7 +107,24 @@ int git_diff_config(const char *var, const char *value)
                diff_rename_limit_default = git_config_int(var, value);
                return 0;
        }
-
+       if (!strcmp(var, "diff.color")) {
+               if (!value)
+                       diff_use_color_default = 1; /* bool */
+               else if (!strcasecmp(value, "auto"))
+                       diff_use_color_default = isatty(1);
+               else if (!strcasecmp(value, "never"))
+                       diff_use_color_default = 0;
+               else if (!strcasecmp(value, "always"))
+                       diff_use_color_default = 1;
+               else
+                       diff_use_color_default = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strncmp(var, "diff.color.", 11)) {
+               int slot = parse_diff_color_slot(var, 11);
+               diff_colors[slot] = parse_diff_color_value(value, var);
+               return 0;
+       }
        return git_default_config(var, value);
 }
 
@@ -165,7 +268,7 @@ static void emit_rewrite_diff(const char *name_a,
 static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
 {
        if (!DIFF_FILE_VALID(one)) {
-               mf->ptr = ""; /* does not matter */
+               mf->ptr = (char *)""; /* does not matter */
                mf->size = 0;
                return 0;
        }
@@ -177,23 +280,56 @@ 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 const char *get_color(int diff_use_color, enum color_diff ix)
+{
+       if (diff_use_color)
+               return diff_colors[ix];
+       return "";
+}
+
+static void fn_out_consume(void *priv, char *line, unsigned long len)
 {
        int i;
        struct emit_callback *ecbdata = priv;
+       const char *set = get_color(ecbdata->color_diff, DIFF_METAINFO);
+       const char *reset = get_color(ecbdata->color_diff, DIFF_RESET);
 
        if (ecbdata->label_path[0]) {
-               printf("--- %s\n", ecbdata->label_path[0]);
-               printf("+++ %s\n", ecbdata->label_path[1]);
+               printf("%s--- %s%s\n", set, ecbdata->label_path[0], reset);
+               printf("%s+++ %s%s\n", set, ecbdata->label_path[1], reset);
                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;
+               set = get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+       }
+       else if (len < ecbdata->nparents)
+               set = reset;
+       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;
+               }
+               set = get_color(ecbdata->color_diff, color);
+       }
+       if (len > 0 && line[len-1] == '\n')
+               len--;
+       printf("%s%.*s%s\n", set, (int) len, line, reset);
 }
 
 static char *pprint_rename(const char *a, const char *b)
@@ -324,7 +460,7 @@ static void show_stats(struct diffstat_t* data)
        }
 
        for (i = 0; i < data->nr; i++) {
-               char *prefix = "";
+               const char *prefix = "";
                char *name = data->files[i]->name;
                int added = data->files[i]->added;
                int deleted = data->files[i]->deleted;
@@ -447,7 +583,7 @@ static unsigned char *deflate_it(char *data,
        z_stream stream;
 
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, Z_BEST_COMPRESSION);
+       deflateInit(&stream, zlib_compression_level);
        bound = deflateBound(&stream, size);
        deflated = xmalloc(bound);
        stream.next_out = deflated;
@@ -515,7 +651,7 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
                else
                        line[0] = bytes - 26 + 'a' - 1;
                encode_85(line + 1, cp, bytes);
-               cp += bytes;
+               cp = (char *) cp + bytes;
                puts(line);
        }
        printf("\n");
@@ -544,30 +680,32 @@ static void builtin_diff(const char *name_a,
        mmfile_t mf1, mf2;
        const char *lbl[2];
        char *a_one, *b_two;
+       const char *set = get_color(o->color_diff, DIFF_METAINFO);
+       const char *reset = get_color(o->color_diff, DIFF_RESET);
 
        a_one = quote_two("a/", 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";
-       printf("diff --git %s %s\n", a_one, b_two);
+       printf("%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
        if (lbl[0][0] == '/') {
                /* /dev/null */
-               printf("new file mode %06o\n", two->mode);
+               printf("%snew file mode %06o%s\n", set, two->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
-                       puts(xfrm_msg);
+                       printf("%s%s%s\n", set, xfrm_msg, reset);
        }
        else if (lbl[1][0] == '/') {
-               printf("deleted file mode %06o\n", one->mode);
+               printf("%sdeleted file mode %06o%s\n", set, one->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
-                       puts(xfrm_msg);
+                       printf("%s%s%s\n", set, xfrm_msg, reset);
        }
        else {
                if (one->mode != two->mode) {
-                       printf("old mode %06o\n", one->mode);
-                       printf("new mode %06o\n", two->mode);
+                       printf("%sold mode %06o%s\n", set, one->mode, reset);
+                       printf("%snew mode %06o%s\n", set, two->mode, reset);
                }
                if (xfrm_msg && xfrm_msg[0])
-                       puts(xfrm_msg);
+                       printf("%s%s%s\n", set, xfrm_msg, reset);
                /*
                 * we do not run diff between different kind
                 * of objects.
@@ -602,8 +740,10 @@ static void builtin_diff(const char *name_a,
                xdemitcb_t ecb;
                struct emit_callback ecbdata;
 
+               memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
-               xpp.flags = XDF_NEED_MINIMAL;
+               ecbdata.color_diff = o->color_diff;
+               xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
                if (!diffopts)
@@ -612,8 +752,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);
        }
 
@@ -627,6 +768,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                             struct diff_filespec *one,
                             struct diff_filespec *two,
                             struct diffstat_t *diffstat,
+                            struct diff_options *o,
                             int complete_rewrite)
 {
        mmfile_t mf1, mf2;
@@ -656,7 +798,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                xdemitconf_t xecfg;
                xdemitcb_t ecb;
 
-               xpp.flags = XDF_NEED_MINIMAL;
+               xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = 0;
                xecfg.flags = 0;
                ecb.outf = xdiff_outf;
@@ -841,7 +983,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
                        err_empty:
                                err = -1;
                        empty:
-                               s->data = "";
+                               s->data = (char *)"";
                                s->size = 0;
                                return err;
                        }
@@ -1241,7 +1383,7 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
 
        if (DIFF_PAIR_UNMERGED(p)) {
                /* unmerged */
-               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, 0);
+               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, 0);
                return;
        }
 
@@ -1253,7 +1395,7 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
 
        if (p->status == DIFF_STATUS_MODIFIED && p->score)
                complete_rewrite = 1;
-       builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite);
+       builtin_diffstat(name, other, p->one, p->two, diffstat, o, complete_rewrite);
 }
 
 static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
@@ -1278,14 +1420,15 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
 void diff_setup(struct diff_options *options)
 {
        memset(options, 0, sizeof(*options));
-       options->output_format = DIFF_FORMAT_RAW;
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
        options->context = 3;
+       options->msg_sep = "";
 
        options->change = diff_change;
        options->add_remove = diff_addremove;
+       options->color_diff = diff_use_color_default;
 }
 
 int diff_setup_done(struct diff_options *options)
@@ -1295,22 +1438,28 @@ int diff_setup_done(struct diff_options *options)
            (0 <= options->rename_limit && !options->detect_rename))
                return -1;
 
+       if (options->output_format & (DIFF_FORMAT_NAME |
+                                     DIFF_FORMAT_NAME_STATUS |
+                                     DIFF_FORMAT_CHECKDIFF |
+                                     DIFF_FORMAT_NO_OUTPUT))
+               options->output_format &= ~(DIFF_FORMAT_RAW |
+                                           DIFF_FORMAT_DIFFSTAT |
+                                           DIFF_FORMAT_SUMMARY |
+                                           DIFF_FORMAT_PATCH);
+
        /*
         * These cases always need recursive; we do not drop caller-supplied
         * recursive bits for other formats here.
         */
-       if ((options->output_format == DIFF_FORMAT_PATCH) ||
-           (options->output_format == DIFF_FORMAT_DIFFSTAT) ||
-           (options->output_format == DIFF_FORMAT_CHECKDIFF))
+       if (options->output_format & (DIFF_FORMAT_PATCH |
+                                     DIFF_FORMAT_DIFFSTAT |
+                                     DIFF_FORMAT_CHECKDIFF))
                options->recursive = 1;
-
        /*
-        * These combinations do not make sense.
+        * Also pickaxe would not work very well if you do not say recursive
         */
-       if (options->output_format == DIFF_FORMAT_RAW)
-               options->with_raw = 0;
-       if (options->output_format == DIFF_FORMAT_DIFFSTAT)
-               options->with_stat  = 0;
+       if (options->pickaxe)
+               options->recursive = 1;
 
        if (options->detect_rename && options->rename_limit < 0)
                options->rename_limit = diff_rename_limit_default;
@@ -1332,7 +1481,7 @@ 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)
+static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
 {
        char c, *eq;
        int len;
@@ -1383,22 +1532,22 @@ 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;
+               options->output_format |= DIFF_FORMAT_PATCH;
        else if (opt_arg(arg, 'U', "unified", &options->context))
-               options->output_format = DIFF_FORMAT_PATCH;
+               options->output_format |= DIFF_FORMAT_PATCH;
+       else if (!strcmp(arg, "--raw"))
+               options->output_format |= DIFF_FORMAT_RAW;
        else if (!strcmp(arg, "--patch-with-raw")) {
-               options->output_format = DIFF_FORMAT_PATCH;
-               options->with_raw = 1;
+               options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
        }
        else if (!strcmp(arg, "--stat"))
-               options->output_format = DIFF_FORMAT_DIFFSTAT;
+               options->output_format |= DIFF_FORMAT_DIFFSTAT;
        else if (!strcmp(arg, "--check"))
-               options->output_format = DIFF_FORMAT_CHECKDIFF;
+               options->output_format |= DIFF_FORMAT_CHECKDIFF;
        else if (!strcmp(arg, "--summary"))
-               options->summary = 1;
+               options->output_format |= DIFF_FORMAT_SUMMARY;
        else if (!strcmp(arg, "--patch-with-stat")) {
-               options->output_format = DIFF_FORMAT_PATCH;
-               options->with_stat = 1;
+               options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT;
        }
        else if (!strcmp(arg, "-z"))
                options->line_termination = 0;
@@ -1407,19 +1556,20 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--full-index"))
                options->full_index = 1;
        else if (!strcmp(arg, "--binary")) {
-               options->output_format = DIFF_FORMAT_PATCH;
+               options->output_format |= DIFF_FORMAT_PATCH;
                options->full_index = options->binary = 1;
        }
        else if (!strcmp(arg, "--name-only"))
-               options->output_format = DIFF_FORMAT_NAME;
+               options->output_format |= DIFF_FORMAT_NAME;
        else if (!strcmp(arg, "--name-status"))
-               options->output_format = DIFF_FORMAT_NAME_STATUS;
+               options->output_format |= DIFF_FORMAT_NAME_STATUS;
        else if (!strcmp(arg, "-R"))
                options->reverse_diff = 1;
        else if (!strncmp(arg, "-S", 2))
                options->pickaxe = arg + 2;
-       else if (!strcmp(arg, "-s"))
-               options->output_format = DIFF_FORMAT_NO_OUTPUT;
+       else if (!strcmp(arg, "-s")) {
+               options->output_format |= DIFF_FORMAT_NO_OUTPUT;
+       }
        else if (!strncmp(arg, "-O", 2))
                options->orderfile = arg + 2;
        else if (!strncmp(arg, "--diff-filter=", 14))
@@ -1456,6 +1606,12 @@ 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 if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
+               options->xdl_opts |= XDF_IGNORE_WHITESPACE;
+       else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
+               options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
        else
                return 0;
        return 1;
@@ -1588,15 +1744,17 @@ const char *diff_unique_abbrev(const unsigned char *sha1, int len)
 }
 
 static void diff_flush_raw(struct diff_filepair *p,
-                          int line_termination,
-                          int inter_name_termination,
-                          struct diff_options *options,
-                          int output_format)
+                          struct diff_options *options)
 {
        int two_paths;
        char status[10];
        int abbrev = options->abbrev;
        const char *path_one, *path_two;
+       int inter_name_termination = '\t';
+       int line_termination = options->line_termination;
+
+       if (!line_termination)
+               inter_name_termination = 0;
 
        path_one = p->one->path;
        path_two = p->two->path;
@@ -1625,7 +1783,7 @@ static void diff_flush_raw(struct diff_filepair *p,
                two_paths = 0;
                break;
        }
-       if (output_format != DIFF_FORMAT_NAME_STATUS) {
+       if (!(options->output_format & DIFF_FORMAT_NAME_STATUS)) {
                printf(":%06o %06o %s ",
                       p->one->mode, p->two->mode,
                       diff_unique_abbrev(p->one->sha1, abbrev));
@@ -1642,16 +1800,12 @@ static void diff_flush_raw(struct diff_filepair *p,
                free((void*)path_two);
 }
 
-static void diff_flush_name(struct diff_filepair *p,
-                           int inter_name_termination,
-                           int line_termination)
+static void diff_flush_name(struct diff_filepair *p, int line_termination)
 {
        char *path = p->two->path;
 
        if (line_termination)
                path = quote_one(p->two->path);
-       else
-               path = p->two->path;
        printf("%s%c", path, line_termination);
        if (p->two->path != path)
                free(path);
@@ -1838,50 +1992,30 @@ static void diff_resolve_rename_copy(void)
        diff_debug_queue("resolve-rename-copy done", q);
 }
 
-static void flush_one_pair(struct diff_filepair *p,
-                          int diff_output_format,
-                          struct diff_options *options,
-                          struct diffstat_t *diffstat)
+static int check_pair_status(struct diff_filepair *p)
 {
-       int inter_name_termination = '\t';
-       int line_termination = options->line_termination;
-       if (!line_termination)
-               inter_name_termination = 0;
-
        switch (p->status) {
        case DIFF_STATUS_UNKNOWN:
-               break;
+               return 0;
        case 0:
                die("internal error in diff-resolve-rename-copy");
-               break;
        default:
-               switch (diff_output_format) {
-               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;
-               case DIFF_FORMAT_RAW:
-               case DIFF_FORMAT_NAME_STATUS:
-                       diff_flush_raw(p, line_termination,
-                                      inter_name_termination,
-                                      options, diff_output_format);
-                       break;
-               case DIFF_FORMAT_NAME:
-                       diff_flush_name(p,
-                                       inter_name_termination,
-                                       line_termination);
-                       break;
-               case DIFF_FORMAT_NO_OUTPUT:
-                       break;
-               }
+               return 1;
        }
 }
 
+static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt)
+{
+       int fmt = opt->output_format;
+
+       if (fmt & DIFF_FORMAT_CHECKDIFF)
+               diff_flush_checkdiff(p, opt);
+       else if (fmt & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS))
+               diff_flush_raw(p, opt);
+       else if (fmt & DIFF_FORMAT_NAME)
+               diff_flush_name(p, opt->line_termination);
+}
+
 static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
 {
        if (fs->mode)
@@ -1961,55 +2095,235 @@ static void diff_summary(struct diff_filepair *p)
        }
 }
 
-void diff_flush(struct diff_options *options)
+struct patch_id_t {
+       struct xdiff_emit_state xm;
+       SHA_CTX *ctx;
+       int patchlen;
+};
+
+static int remove_space(char *line, int len)
+{
+       int i;
+        char *dst = line;
+        unsigned char c;
+
+        for (i = 0; i < len; i++)
+                if (!isspace((c = line[i])))
+                        *dst++ = c;
+
+        return dst - line;
+}
+
+static void patch_id_consume(void *priv, char *line, unsigned long len)
+{
+       struct patch_id_t *data = priv;
+       int new_len;
+
+       /* Ignore line numbers when computing the SHA1 of the patch */
+       if (!strncmp(line, "@@ -", 4))
+               return;
+
+       new_len = remove_space(line, len);
+
+       SHA1_Update(data->ctx, line, new_len);
+       data->patchlen += new_len;
+}
+
+/* returns 0 upon success, and writes result into sha1 */
+static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i;
+       SHA_CTX ctx;
+       struct patch_id_t data;
+       char buffer[PATH_MAX * 4 + 20];
+
+       SHA1_Init(&ctx);
+       memset(&data, 0, sizeof(struct patch_id_t));
+       data.ctx = &ctx;
+       data.xm.consume = patch_id_consume;
+
+       for (i = 0; i < q->nr; i++) {
+               xpparam_t xpp;
+               xdemitconf_t xecfg;
+               xdemitcb_t ecb;
+               mmfile_t mf1, mf2;
+               struct diff_filepair *p = q->queue[i];
+               int len1, len2;
+
+               if (p->status == 0)
+                       return error("internal diff status error");
+               if (p->status == DIFF_STATUS_UNKNOWN)
+                       continue;
+               if (diff_unmodified_pair(p))
+                       continue;
+               if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+                   (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+                       continue;
+               if (DIFF_PAIR_UNMERGED(p))
+                       continue;
+
+               diff_fill_sha1_info(p->one);
+               diff_fill_sha1_info(p->two);
+               if (fill_mmfile(&mf1, p->one) < 0 ||
+                               fill_mmfile(&mf2, p->two) < 0)
+                       return error("unable to read files to diff");
+
+               /* Maybe hash p->two? into the patch id? */
+               if (mmfile_is_binary(&mf2))
+                       continue;
+
+               len1 = remove_space(p->one->path, strlen(p->one->path));
+               len2 = remove_space(p->two->path, strlen(p->two->path));
+               if (p->one->mode == 0)
+                       len1 = snprintf(buffer, sizeof(buffer),
+                                       "diff--gita/%.*sb/%.*s"
+                                       "newfilemode%06o"
+                                       "---/dev/null"
+                                       "+++b/%.*s",
+                                       len1, p->one->path,
+                                       len2, p->two->path,
+                                       p->two->mode,
+                                       len2, p->two->path);
+               else if (p->two->mode == 0)
+                       len1 = snprintf(buffer, sizeof(buffer),
+                                       "diff--gita/%.*sb/%.*s"
+                                       "deletedfilemode%06o"
+                                       "---a/%.*s"
+                                       "+++/dev/null",
+                                       len1, p->one->path,
+                                       len2, p->two->path,
+                                       p->one->mode,
+                                       len1, p->one->path);
+               else
+                       len1 = snprintf(buffer, sizeof(buffer),
+                                       "diff--gita/%.*sb/%.*s"
+                                       "---a/%.*s"
+                                       "+++b/%.*s",
+                                       len1, p->one->path,
+                                       len2, p->two->path,
+                                       len1, p->one->path,
+                                       len2, p->two->path);
+               SHA1_Update(&ctx, buffer, len1);
+
+               xpp.flags = XDF_NEED_MINIMAL;
+               xecfg.ctxlen = 3;
+               xecfg.flags = XDL_EMIT_FUNCNAMES;
+               ecb.outf = xdiff_outf;
+               ecb.priv = &data;
+               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+       }
+
+       SHA1_Final(sha1, &ctx);
+       return 0;
+}
+
+int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
-       int diff_output_format = options->output_format;
-       struct diffstat_t *diffstat = NULL;
+       int result = diff_get_patch_id(options, sha1);
+
+       for (i = 0; i < q->nr; i++)
+               diff_free_filepair(q->queue[i]);
+
+       free(q->queue);
+       q->queue = NULL;
+       q->nr = q->alloc = 0;
+
+       return result;
+}
 
-       if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
-               diffstat = xcalloc(sizeof (struct diffstat_t), 1);
-               diffstat->xm.consume = diffstat_consume;
+static int is_summary_empty(const struct diff_queue_struct *q)
+{
+       int i;
+
+       for (i = 0; i < q->nr; i++) {
+               const struct diff_filepair *p = q->queue[i];
+
+               switch (p->status) {
+               case DIFF_STATUS_DELETED:
+               case DIFF_STATUS_ADDED:
+               case DIFF_STATUS_COPIED:
+               case DIFF_STATUS_RENAMED:
+                       return 0;
+               default:
+                       if (p->score)
+                               return 0;
+                       if (p->one->mode && p->two->mode &&
+                           p->one->mode != p->two->mode)
+                               return 0;
+                       break;
+               }
        }
+       return 1;
+}
+
+void diff_flush(struct diff_options *options)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i, output_format = options->output_format;
+       int separator = 0;
+
+       /*
+        * Order: raw, stat, summary, patch
+        * or:    name/name-status/checkdiff (other bits clear)
+        */
+       if (!q->nr)
+               goto free_queue;
 
-       if (options->with_raw) {
+       if (output_format & (DIFF_FORMAT_RAW |
+                            DIFF_FORMAT_NAME |
+                            DIFF_FORMAT_NAME_STATUS |
+                            DIFF_FORMAT_CHECKDIFF)) {
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
-                       flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
+                       if (check_pair_status(p))
+                               flush_one_pair(p, options);
                }
-               putchar(options->line_termination);
+               separator++;
        }
-       if (options->with_stat) {
+
+       if (output_format & DIFF_FORMAT_DIFFSTAT) {
+               struct diffstat_t diffstat;
+
+               memset(&diffstat, 0, sizeof(struct diffstat_t));
+               diffstat.xm.consume = diffstat_consume;
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
-                       flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
-                                      diffstat);
+                       if (check_pair_status(p))
+                               diff_flush_stat(p, options, &diffstat);
                }
-               show_stats(diffstat);
-               free(diffstat);
-               diffstat = NULL;
-               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);
+               show_stats(&diffstat);
+               separator++;
        }
 
-       if (diffstat) {
-               show_stats(diffstat);
-               free(diffstat);
+       if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) {
+               for (i = 0; i < q->nr; i++)
+                       diff_summary(q->queue[i]);
+               separator++;
        }
 
-       for (i = 0; i < q->nr; i++) {
-               if (options->summary)
-                       diff_summary(q->queue[i]);
-               diff_free_filepair(q->queue[i]);
+       if (output_format & DIFF_FORMAT_PATCH) {
+               if (separator) {
+                       if (options->stat_sep) {
+                               /* attach patch instead of inline */
+                               fputs(options->stat_sep, stdout);
+                       } else {
+                               putchar(options->line_termination);
+                       }
+               }
+
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (check_pair_status(p))
+                               diff_flush_patch(p, options);
+               }
        }
 
+       for (i = 0; i < q->nr; i++)
+               diff_free_filepair(q->queue[i]);
+free_queue:
        free(q->queue);
        q->queue = NULL;
        q->nr = q->alloc = 0;