Code

Merge branch 'th/diff-extra'
[git.git] / diff.c
diff --git a/diff.c b/diff.c
index 5358fd0c14a249c2d1a3fe417016264aba6fc745..8861b853e70ab511fdcab16d97687aafc99ec000 100644 (file)
--- a/diff.c
+++ b/diff.c
 
 static int use_size_cache;
 
+static int diff_detect_rename_default = 0;
 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,
+/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
+static char diff_colors[][24] = {
+       "\033[m",       /* reset */
+       "",             /* normal */
+       "\033[1m",      /* bold */
+       "\033[36m",     /* cyan */
+       "\033[31m",     /* red */
+       "\033[32m",     /* green */
+       "\033[33m"      /* yellow */
 };
 
 static int parse_diff_color_slot(const char *var, int ofs)
@@ -63,45 +40,131 @@ static int parse_diff_color_slot(const char *var, int ofs)
                return DIFF_FILE_OLD;
        if (!strcasecmp(var+ofs, "new"))
                return DIFF_FILE_NEW;
+       if (!strcasecmp(var+ofs, "commit"))
+               return DIFF_COMMIT;
        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;
+static int parse_color(const char *name, int len)
+{
+       static const char * const color_names[] = {
+               "normal", "black", "red", "green", "yellow",
+               "blue", "magenta", "cyan", "white"
+       };
+       char *end;
+       int i;
+       for (i = 0; i < ARRAY_SIZE(color_names); i++) {
+               const char *str = color_names[i];
+               if (!strncasecmp(name, str, len) && !str[len])
+                       return i - 1;
+       }
+       i = strtol(name, &end, 10);
+       if (*name && !*end && i >= -1 && i <= 255)
+               return i;
+       return -2;
+}
+
+static int parse_attr(const char *name, int len)
+{
+       static const int attr_values[] = { 1, 2, 4, 5, 7 };
+       static const char * const attr_names[] = {
+               "bold", "dim", "ul", "blink", "reverse"
+       };
+       int i;
+       for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
+               const char *str = attr_names[i];
+               if (!strncasecmp(name, str, len) && !str[len])
+                       return attr_values[i];
+       }
+       return -1;
+}
+
+static void parse_diff_color_value(const char *value, const char *var, char *dst)
+{
+       const char *ptr = value;
+       int attr = -1;
+       int fg = -2;
+       int bg = -2;
+
+       if (!strcasecmp(value, "reset")) {
+               strcpy(dst, "\033[m");
+               return;
+       }
+
+       /* [fg [bg]] [attr] */
+       while (*ptr) {
+               const char *word = ptr;
+               int val, len = 0;
+
+               while (word[len] && !isspace(word[len]))
+                       len++;
+
+               ptr = word + len;
+               while (*ptr && isspace(*ptr))
+                       ptr++;
+
+               val = parse_color(word, len);
+               if (val >= -1) {
+                       if (fg == -2) {
+                               fg = val;
+                               continue;
+                       }
+                       if (bg == -2) {
+                               bg = val;
+                               continue;
+                       }
+                       goto bad;
+               }
+               val = parse_attr(word, len);
+               if (val < 0 || attr != -1)
+                       goto bad;
+               attr = val;
+       }
+
+       if (attr >= 0 || fg >= 0 || bg >= 0) {
+               int sep = 0;
+
+               *dst++ = '\033';
+               *dst++ = '[';
+               if (attr >= 0) {
+                       *dst++ = '0' + attr;
+                       sep++;
+               }
+               if (fg >= 0) {
+                       if (sep++)
+                               *dst++ = ';';
+                       if (fg < 8) {
+                               *dst++ = '3';
+                               *dst++ = '0' + fg;
+                       } else {
+                               dst += sprintf(dst, "38;5;%d", fg);
+                       }
+               }
+               if (bg >= 0) {
+                       if (sep++)
+                               *dst++ = ';';
+                       if (bg < 8) {
+                               *dst++ = '4';
+                               *dst++ = '0' + bg;
+                       } else {
+                               dst += sprintf(dst, "48;5;%d", bg);
+                       }
+               }
+               *dst++ = 'm';
+       }
+       *dst = 0;
+       return;
+bad:
        die("bad config value '%s' for variable '%s'", value, var);
 }
 
-int git_diff_config(const char *var, const char *value)
+/*
+ * These are to give UI layer defaults.
+ * The core-level commands such as git-diff-files should
+ * never be affected by the setting of diff.renames
+ * the user happens to have in the configuration file.
+ */
+int git_diff_ui_config(const char *var, const char *value)
 {
        if (!strcmp(var, "diff.renamelimit")) {
                diff_rename_limit_default = git_config_int(var, value);
@@ -110,8 +173,14 @@ int git_diff_config(const char *var, const char *value)
        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, "auto")) {
+                       diff_use_color_default = 0;
+                       if (isatty(1) || (pager_in_use && pager_use_color)) {
+                               char *term = getenv("TERM");
+                               if (term && strcmp(term, "dumb"))
+                                       diff_use_color_default = 1;
+                       }
+               }
                else if (!strcasecmp(value, "never"))
                        diff_use_color_default = 0;
                else if (!strcasecmp(value, "always"))
@@ -120,9 +189,19 @@ int git_diff_config(const char *var, const char *value)
                        diff_use_color_default = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "diff.renames")) {
+               if (!value)
+                       diff_detect_rename_default = DIFF_DETECT_RENAME;
+               else if (!strcasecmp(value, "copies") ||
+                        !strcasecmp(value, "copy"))
+                       diff_detect_rename_default = DIFF_DETECT_COPY;
+               else if (git_config_bool(var,value))
+                       diff_detect_rename_default = DIFF_DETECT_RENAME;
+               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);
+               parse_diff_color_value(value, var, diff_colors[slot]);
                return 0;
        }
        return git_default_config(var, value);
@@ -279,13 +358,153 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
        return 0;
 }
 
+struct diff_words_buffer {
+       mmfile_t text;
+       long alloc;
+       long current; /* output pointer */
+       int suppressed_newline;
+};
+
+static void diff_words_append(char *line, unsigned long len,
+               struct diff_words_buffer *buffer)
+{
+       if (buffer->text.size + len > buffer->alloc) {
+               buffer->alloc = (buffer->text.size + len) * 3 / 2;
+               buffer->text.ptr = xrealloc(buffer->text.ptr, buffer->alloc);
+       }
+       line++;
+       len--;
+       memcpy(buffer->text.ptr + buffer->text.size, line, len);
+       buffer->text.size += len;
+}
+
+struct diff_words_data {
+       struct xdiff_emit_state xm;
+       struct diff_words_buffer minus, plus;
+};
+
+static void print_word(struct diff_words_buffer *buffer, int len, int color,
+               int suppress_newline)
+{
+       const char *ptr;
+       int eol = 0;
+
+       if (len == 0)
+               return;
+
+       ptr  = buffer->text.ptr + buffer->current;
+       buffer->current += len;
+
+       if (ptr[len - 1] == '\n') {
+               eol = 1;
+               len--;
+       }
+
+       fputs(diff_get_color(1, color), stdout);
+       fwrite(ptr, len, 1, stdout);
+       fputs(diff_get_color(1, DIFF_RESET), stdout);
+
+       if (eol) {
+               if (suppress_newline)
+                       buffer->suppressed_newline = 1;
+               else
+                       putchar('\n');
+       }
+}
+
+static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
+{
+       struct diff_words_data *diff_words = priv;
+
+       if (diff_words->minus.suppressed_newline) {
+               if (line[0] != '+')
+                       putchar('\n');
+               diff_words->minus.suppressed_newline = 0;
+       }
+
+       len--;
+       switch (line[0]) {
+               case '-':
+                       print_word(&diff_words->minus, len, DIFF_FILE_OLD, 1);
+                       break;
+               case '+':
+                       print_word(&diff_words->plus, len, DIFF_FILE_NEW, 0);
+                       break;
+               case ' ':
+                       print_word(&diff_words->plus, len, DIFF_PLAIN, 0);
+                       diff_words->minus.current += len;
+                       break;
+       }
+}
+
+/* this executes the word diff on the accumulated buffers */
+static void diff_words_show(struct diff_words_data *diff_words)
+{
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+       mmfile_t minus, plus;
+       int i;
+
+       minus.size = diff_words->minus.text.size;
+       minus.ptr = xmalloc(minus.size);
+       memcpy(minus.ptr, diff_words->minus.text.ptr, minus.size);
+       for (i = 0; i < minus.size; i++)
+               if (isspace(minus.ptr[i]))
+                       minus.ptr[i] = '\n';
+       diff_words->minus.current = 0;
+
+       plus.size = diff_words->plus.text.size;
+       plus.ptr = xmalloc(plus.size);
+       memcpy(plus.ptr, diff_words->plus.text.ptr, plus.size);
+       for (i = 0; i < plus.size; i++)
+               if (isspace(plus.ptr[i]))
+                       plus.ptr[i] = '\n';
+       diff_words->plus.current = 0;
+
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc;
+       xecfg.flags = 0;
+       ecb.outf = xdiff_outf;
+       ecb.priv = diff_words;
+       diff_words->xm.consume = fn_out_diff_words_aux;
+       xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+
+       free(minus.ptr);
+       free(plus.ptr);
+       diff_words->minus.text.size = diff_words->plus.text.size = 0;
+
+       if (diff_words->minus.suppressed_newline) {
+               putchar('\n');
+               diff_words->minus.suppressed_newline = 0;
+       }
+}
+
 struct emit_callback {
        struct xdiff_emit_state xm;
        int nparents, color_diff;
        const char **label_path;
+       struct diff_words_data *diff_words;
 };
 
-static inline const char *get_color(int diff_use_color, enum color_diff ix)
+static void free_diff_words_data(struct emit_callback *ecbdata)
+{
+       if (ecbdata->diff_words) {
+               /* flush buffers */
+               if (ecbdata->diff_words->minus.text.size ||
+                               ecbdata->diff_words->plus.text.size)
+                       diff_words_show(ecbdata->diff_words);
+
+               if (ecbdata->diff_words->minus.text.ptr)
+                       free (ecbdata->diff_words->minus.text.ptr);
+               if (ecbdata->diff_words->plus.text.ptr)
+                       free (ecbdata->diff_words->plus.text.ptr);
+               free(ecbdata->diff_words);
+               ecbdata->diff_words = NULL;
+       }
+}
+
+const char *diff_get_color(int diff_use_color, enum color_diff ix)
 {
        if (diff_use_color)
                return diff_colors[ix];
@@ -296,8 +515,8 @@ 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);
+       const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
+       const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
 
        if (ecbdata->label_path[0]) {
                printf("%s--- %s%s\n", set, ecbdata->label_path[0], reset);
@@ -312,24 +531,45 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                ;
        if (2 <= i && i < len && line[i] == ' ') {
                ecbdata->nparents = i - 1;
-               set = get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+               set = diff_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 (ecbdata->diff_words && nparents != 1)
+                       /* fall back to normal diff */
+                       free_diff_words_data(ecbdata);
+               if (ecbdata->diff_words) {
+                       if (line[0] == '-') {
+                               diff_words_append(line, len,
+                                               &ecbdata->diff_words->minus);
+                               return;
+                       } else if (line[0] == '+') {
+                               diff_words_append(line, len,
+                                               &ecbdata->diff_words->plus);
+                               return;
+                       }
+                       if (ecbdata->diff_words->minus.text.size ||
+                                       ecbdata->diff_words->plus.text.size)
+                               diff_words_show(ecbdata->diff_words);
+                       line++;
+                       len--;
+               } else
+                       for (i = 0; i < nparents && len; i++) {
+                               if (line[i] == '-')
+                                       color = DIFF_FILE_OLD;
+                               else if (line[i] == '+')
+                                       color = DIFF_FILE_NEW;
+                       }
+               set = diff_get_color(ecbdata->color_diff, color);
        }
        if (len > 0 && line[len-1] == '\n')
                len--;
-       printf("%s%.*s%s\n", set, (int) len, line, reset);
+       fputs (set, stdout);
+       fwrite (line, len, 1, stdout);
+       puts (reset);
 }
 
 static char *pprint_rename(const char *a, const char *b)
@@ -583,7 +823,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;
@@ -680,8 +920,8 @@ 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);
+       const char *set = diff_get_color(o->color_diff, DIFF_METAINFO);
+       const char *reset = diff_get_color(o->color_diff, DIFF_RESET);
 
        a_one = quote_two("a/", name_a);
        b_two = quote_two("b/", name_b);
@@ -721,7 +961,7 @@ static void builtin_diff(const char *name_a,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
 
-       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) {
+       if (!o->text && (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))) {
                /* Quite common confusing case */
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
@@ -755,7 +995,12 @@ static void builtin_diff(const char *name_a,
                ecb.outf = xdiff_outf;
                ecb.priv = &ecbdata;
                ecbdata.xm.consume = fn_out_consume;
+               if (o->color_diff_words)
+                       ecbdata.diff_words =
+                               xcalloc(1, sizeof(struct diff_words_data));
                xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+               if (o->color_diff_words)
+                       free_diff_words_data(&ecbdata);
        }
 
  free_ab_and_return:
@@ -1429,6 +1674,7 @@ void diff_setup(struct diff_options *options)
        options->change = diff_change;
        options->add_remove = diff_addremove;
        options->color_diff = diff_use_color_default;
+       options->detect_rename = diff_detect_rename_default;
 }
 
 int diff_setup_done(struct diff_options *options)
@@ -1446,10 +1692,8 @@ int diff_setup_done(struct diff_options *options)
        if (count > 1)
                die("--name-only, --name-status, --check and -s are mutually exclusive");
 
-       if ((options->find_copies_harder &&
-            options->detect_rename != DIFF_DETECT_COPY) ||
-           (0 <= options->rename_limit && !options->detect_rename))
-               return -1;
+       if (options->find_copies_harder)
+               options->detect_rename = DIFF_DETECT_COPY;
 
        if (options->output_format & (DIFF_FORMAT_NAME |
                                      DIFF_FORMAT_NAME_STATUS |
@@ -1572,6 +1816,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->output_format |= DIFF_FORMAT_PATCH;
                options->full_index = options->binary = 1;
        }
+       else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) {
+               options->text = 1;
+       }
        else if (!strcmp(arg, "--name-only"))
                options->output_format |= DIFF_FORMAT_NAME;
        else if (!strcmp(arg, "--name-status"))
@@ -1621,10 +1868,16 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        }
        else if (!strcmp(arg, "--color"))
                options->color_diff = 1;
+       else if (!strcmp(arg, "--no-color"))
+               options->color_diff = 0;
        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 if (!strcmp(arg, "--color-words"))
+               options->color_diff = options->color_diff_words = 1;
+       else if (!strcmp(arg, "--no-renames"))
+               options->detect_rename = 0;
        else
                return 0;
        return 1;
@@ -1710,13 +1963,9 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
                                 struct diff_filespec *one,
                                 struct diff_filespec *two)
 {
-       struct diff_filepair *dp = xmalloc(sizeof(*dp));
+       struct diff_filepair *dp = xcalloc(1, sizeof(*dp));
        dp->one = one;
        dp->two = two;
-       dp->score = 0;
-       dp->status = 0;
-       dp->source_stays = 0;
-       dp->broken_pair = 0;
        if (queue)
                diff_q(queue, dp);
        return dp;
@@ -2108,6 +2357,145 @@ static void diff_summary(struct diff_filepair *p)
        }
 }
 
+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 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;
+}
+
 static int is_summary_empty(const struct diff_queue_struct *q)
 {
        int i;