Support for pickaxe matching regular expressions
[gitweb.git] / diff.c
diff --git a/diff.c b/diff.c
index c73a40b9ab3abe3e4a1ddd51a23a19be36b07ea4..2db2cc568dae9e94f56343cb1c900a2da195c9d0 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -8,8 +8,7 @@
 #include "quote.h"
 #include "diff.h"
 #include "diffcore.h"
-
-static const char *diff_opts = "-pu";
+#include "xdiff/xdiff.h"
 
 static int use_size_cache;
 
@@ -69,25 +68,10 @@ static const char *external_diff(void)
 {
        static const char *external_diff_cmd = NULL;
        static int done_preparing = 0;
-       const char *env_diff_opts;
 
        if (done_preparing)
                return external_diff_cmd;
-
-       /*
-        * Default values above are meant to match the
-        * Linux kernel development style.  Examples of
-        * alternative styles you can specify via environment
-        * variables are:
-        *
-        * GIT_DIFF_OPTS="-c";
-        */
        external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
-
-       /* In case external diff fails... */
-       env_diff_opts = getenv("GIT_DIFF_OPTS");
-       if (env_diff_opts) diff_opts = env_diff_opts;
-
        done_preparing = 1;
        return external_diff_cmd;
 }
@@ -101,13 +85,12 @@ static struct diff_tempfile {
        char tmp_path[TEMPFILE_PATH_LEN];
 } diff_temp[2];
 
-static int count_lines(const char *filename)
+static int count_lines(const char *data, int size)
 {
-       FILE *in;
        int count, ch, completely_empty = 1, nl_just_seen = 0;
-       in = fopen(filename, "r");
        count = 0;
-       while ((ch = fgetc(in)) != EOF)
+       while (0 < size--) {
+               ch = *data++;
                if (ch == '\n') {
                        count++;
                        nl_just_seen = 1;
@@ -117,7 +100,7 @@ static int count_lines(const char *filename)
                        nl_just_seen = 0;
                        completely_empty = 0;
                }
-       fclose(in);
+       }
        if (completely_empty)
                return 0;
        if (!nl_just_seen)
@@ -140,12 +123,11 @@ static void print_line_count(int count)
        }
 }
 
-static void copy_file(int prefix, const char *filename)
+static void copy_file(int prefix, const char *data, int size)
 {
-       FILE *in;
        int ch, nl_just_seen = 1;
-       in = fopen(filename, "r");
-       while ((ch = fgetc(in)) != EOF) {
+       while (0 < size--) {
+               ch = *data++;
                if (nl_just_seen)
                        putchar(prefix);
                putchar(ch);
@@ -154,92 +136,106 @@ static void copy_file(int prefix, const char *filename)
                else
                        nl_just_seen = 0;
        }
-       fclose(in);
        if (!nl_just_seen)
                printf("\n\\ No newline at end of file\n");
 }
 
 static void emit_rewrite_diff(const char *name_a,
                              const char *name_b,
-                             struct diff_tempfile *temp)
+                             struct diff_filespec *one, 
+                             struct diff_filespec *two)
 {
        /* Use temp[i].name as input, name_a and name_b as labels */
        int lc_a, lc_b;
-       lc_a = count_lines(temp[0].name);
-       lc_b = count_lines(temp[1].name);
+       lc_a = count_lines(one->data, one->size);
+       lc_b = count_lines(two->data, two->size);
        printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
        print_line_count(lc_a);
        printf(" +");
        print_line_count(lc_b);
        printf(" @@\n");
        if (lc_a)
-               copy_file('-', temp[0].name);
+               copy_file('-', one->data, one->size);
        if (lc_b)
-               copy_file('+', temp[1].name);
+               copy_file('+', two->data, two->size);
 }
 
-static const char *builtin_diff(const char *name_a,
-                        const char *name_b,
-                        struct diff_tempfile *temp,
-                        const char *xfrm_msg,
-                        int complete_rewrite,
-                        const char **args)
+static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
 {
-       int i, next_at, cmd_size;
-       const char *const diff_cmd = "diff -L%s -L%s";
-       const char *const diff_arg  = "-- %s %s||:"; /* "||:" is to return 0 */
-       const char *input_name_sq[2];
-       const char *label_path[2];
-       char *cmd;
-
-       /* diff_cmd and diff_arg have 4 %s in total which makes
-        * the sum of these strings 8 bytes larger than required.
-        * we use 2 spaces around diff-opts, and we need to count
-        * terminating NUL; we used to subtract 5 here, but we do not
-        * care about small leaks in this subprocess that is about
-        * to exec "diff" anymore.
-        */
-       cmd_size = (strlen(diff_cmd) + strlen(diff_opts) + strlen(diff_arg)
-                   + 128);
-
-       for (i = 0; i < 2; i++) {
-               input_name_sq[i] = sq_quote(temp[i].name);
-               if (!strcmp(temp[i].name, "/dev/null"))
-                       label_path[i] = "/dev/null";
-               else if (!i)
-                       label_path[i] = sq_quote(quote_two("a/", name_a));
-               else
-                       label_path[i] = sq_quote(quote_two("b/", name_b));
-               cmd_size += (strlen(label_path[i]) + strlen(input_name_sq[i]));
+       if (!DIFF_FILE_VALID(one)) {
+               mf->ptr = ""; /* does not matter */
+               mf->size = 0;
+               return 0;
+       }
+       else if (diff_populate_filespec(one, 0))
+               return -1;
+       mf->ptr = one->data;
+       mf->size = one->size;
+       return 0;
+}
+
+struct emit_callback {
+       const char **label_path;
+};
+
+static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
+{
+       int i;
+       struct emit_callback *ecbdata = priv;
+
+       if (ecbdata->label_path[0]) {
+               printf("--- %s\n", ecbdata->label_path[0]);
+               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;
+}
+
+#define FIRST_FEW_BYTES 8000
+static int mmfile_is_binary(mmfile_t *mf)
+{
+       long sz = mf->size;
+       if (FIRST_FEW_BYTES < sz)
+               sz = FIRST_FEW_BYTES;
+       if (memchr(mf->ptr, 0, sz))
+               return 1;
+       return 0;
+}
 
-       cmd = xmalloc(cmd_size);
-
-       next_at = 0;
-       next_at += snprintf(cmd+next_at, cmd_size-next_at,
-                           diff_cmd, label_path[0], label_path[1]);
-       next_at += snprintf(cmd+next_at, cmd_size-next_at,
-                           " %s ", diff_opts);
-       next_at += snprintf(cmd+next_at, cmd_size-next_at,
-                           diff_arg, input_name_sq[0], input_name_sq[1]);
-
-       printf("diff --git %s %s\n",
-              quote_two("a/", name_a), quote_two("b/", name_b));
-       if (label_path[0][0] == '/') {
-               /* dev/null */
-               printf("new file mode %s\n", temp[1].mode);
+static void builtin_diff(const char *name_a,
+                        const char *name_b,
+                        struct diff_filespec *one,
+                        struct diff_filespec *two,
+                        const char *xfrm_msg,
+                        int complete_rewrite)
+{
+       mmfile_t mf1, mf2;
+       const char *lbl[2];
+       char *a_one, *b_two;
+
+       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);
+       if (lbl[0][0] == '/') {
+               /* /dev/null */
+               printf("new file mode %06o\n", two->mode);
                if (xfrm_msg && xfrm_msg[0])
                        puts(xfrm_msg);
        }
-       else if (label_path[1][0] == '/') {
-               printf("deleted file mode %s\n", temp[0].mode);
+       else if (lbl[1][0] == '/') {
+               printf("deleted file mode %06o\n", one->mode);
                if (xfrm_msg && xfrm_msg[0])
                        puts(xfrm_msg);
        }
        else {
-               if (strcmp(temp[0].mode, temp[1].mode)) {
-                       printf("old mode %s\n", temp[0].mode);
-                       printf("new mode %s\n", temp[1].mode);
+               if (one->mode != two->mode) {
+                       printf("old mode %06o\n", one->mode);
+                       printf("new mode %06o\n", two->mode);
                }
                if (xfrm_msg && xfrm_msg[0])
                        puts(xfrm_msg);
@@ -247,20 +243,46 @@ static const char *builtin_diff(const char *name_a,
                 * we do not run diff between different kind
                 * of objects.
                 */
-               if (strncmp(temp[0].mode, temp[1].mode, 3))
-                       return NULL;
+               if ((one->mode ^ two->mode) & S_IFMT)
+                       goto free_ab_and_return;
                if (complete_rewrite) {
-                       emit_rewrite_diff(name_a, name_b, temp);
-                       return NULL;
+                       emit_rewrite_diff(name_a, name_b, one, two);
+                       goto free_ab_and_return;
                }
        }
 
-       /* This is disgusting */
-       *args++ = "sh";
-       *args++ = "-c";
-       *args++ = cmd;
-       *args = NULL;
-       return "/bin/sh";
+       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))
+               printf("Binary files %s and %s differ\n", lbl[0], lbl[1]);
+       else {
+               /* Crazy xdl interfaces.. */
+               const char *diffopts = getenv("GIT_DIFF_OPTS");
+               xpparam_t xpp;
+               xdemitconf_t xecfg;
+               xdemitcb_t ecb;
+               struct emit_callback ecbdata;
+
+               ecbdata.label_path = lbl;
+               xpp.flags = XDF_NEED_MINIMAL;
+               xecfg.ctxlen = 3;
+               xecfg.flags = XDL_EMIT_FUNCNAMES;
+               if (!diffopts)
+                       ;
+               else if (!strncmp(diffopts, "--unified=", 10))
+                       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.priv = &ecbdata;
+               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+       }
+
+ free_ab_and_return:
+       free(a_one);
+       free(b_two);
+       return;
 }
 
 struct diff_filespec *alloc_filespec(const char *path)
@@ -278,7 +300,7 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
                   unsigned short mode)
 {
        if (mode) {
-               spec->mode = DIFF_FILE_CANON_MODE(mode);
+               spec->mode = canon_mode(mode);
                memcpy(spec->sha1, sha1, 20);
                spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
        }
@@ -620,6 +642,7 @@ static void run_external_diff(const char *pgm,
        int retval;
        static int atexit_asked = 0;
        const char *othername;
+       const char **arg = &spawn_arg[0];
 
        othername = (other? other : name);
        if (one && two) {
@@ -634,36 +657,25 @@ static void run_external_diff(const char *pgm,
                signal(SIGINT, remove_tempfile_on_signal);
        }
 
-       if (pgm) {
-               const char **arg = &spawn_arg[0];
-               if (one && two) {
-                       *arg++ = pgm;
-                       *arg++ = name;
-                       *arg++ = temp[0].name;
-                       *arg++ = temp[0].hex;
-                       *arg++ = temp[0].mode;
-                       *arg++ = temp[1].name;
-                       *arg++ = temp[1].hex;
-                       *arg++ = temp[1].mode;
-                       if (other) {
-                               *arg++ = other;
-                               *arg++ = xfrm_msg;
-                       }
-               } else {
-                       *arg++ = pgm;
-                       *arg++ = name;
+       if (one && two) {
+               *arg++ = pgm;
+               *arg++ = name;
+               *arg++ = temp[0].name;
+               *arg++ = temp[0].hex;
+               *arg++ = temp[0].mode;
+               *arg++ = temp[1].name;
+               *arg++ = temp[1].hex;
+               *arg++ = temp[1].mode;
+               if (other) {
+                       *arg++ = other;
+                       *arg++ = xfrm_msg;
                }
-               *arg = NULL;
        } else {
-               if (one && two) {
-                       pgm = builtin_diff(name, othername, temp, xfrm_msg, complete_rewrite, spawn_arg);
-               } else
-                       printf("* Unmerged path %s\n", name);
+               *arg++ = pgm;
+               *arg++ = name;
        }
-
-       retval = 0;
-       if (pgm)
-               retval = spawn_prog(pgm, spawn_arg);
+       *arg = NULL;
+       retval = spawn_prog(pgm, spawn_arg);
        remove_tempfile();
        if (retval) {
                fprintf(stderr, "external diff died, stopping at %s.\n", name);
@@ -671,6 +683,26 @@ static void run_external_diff(const char *pgm,
        }
 }
 
+static void run_diff_cmd(const char *pgm,
+                        const char *name,
+                        const char *other,
+                        struct diff_filespec *one,
+                        struct diff_filespec *two,
+                        const char *xfrm_msg,
+                        int complete_rewrite)
+{
+       if (pgm) {
+               run_external_diff(pgm, name, other, one, two, xfrm_msg,
+                                 complete_rewrite);
+               return;
+       }
+       if (one && two)
+               builtin_diff(name, other ? other : name,
+                            one, two, xfrm_msg, complete_rewrite);
+       else
+               printf("* Unmerged path %s\n", name);
+}
+
 static void diff_fill_sha1_info(struct diff_filespec *one)
 {
        if (DIFF_FILE_VALID(one)) {
@@ -700,8 +732,7 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
 
        if (DIFF_PAIR_UNMERGED(p)) {
                /* unmerged */
-               run_external_diff(pgm, p->one->path, NULL, NULL, NULL, NULL,
-                                 0);
+               run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, 0);
                return;
        }
 
@@ -773,15 +804,15 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
                 * needs to be split into deletion and creation.
                 */
                struct diff_filespec *null = alloc_filespec(two->path);
-               run_external_diff(NULL, name, other, one, null, xfrm_msg, 0);
+               run_diff_cmd(NULL, name, other, one, null, xfrm_msg, 0);
                free(null);
                null = alloc_filespec(one->path);
-               run_external_diff(NULL, name, other, null, two, xfrm_msg, 0);
+               run_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0);
                free(null);
        }
        else
-               run_external_diff(pgm, name, other, one, two, xfrm_msg,
-                                 complete_rewrite);
+               run_diff_cmd(pgm, name, other, one, two, xfrm_msg,
+                            complete_rewrite);
 
        free(name_munged);
        free(other_munged);
@@ -852,6 +883,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->filter = arg + 14;
        else if (!strcmp(arg, "--pickaxe-all"))
                options->pickaxe_opts = DIFF_PICKAXE_ALL;
+       else if (!strcmp(arg, "--pickaxe-regex"))
+               options->pickaxe_opts = DIFF_PICKAXE_REGEX;
        else if (!strncmp(arg, "-B", 2)) {
                if ((options->break_opt =
                     diff_scoreopt_parse(arg)) == -1)