Merge branch 'ab/blame-textconv'
authorJunio C Hamano <gitster@pobox.com>
Sun, 27 Jun 2010 19:07:44 +0000 (12:07 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 27 Jun 2010 19:07:44 +0000 (12:07 -0700)
* ab/blame-textconv:
t/t8006: test textconv support for blame
textconv: support for blame
textconv: make the API public

Conflicts:
diff.h

builtin/blame.c
diff.c
diff.h
t/t8006-blame-textconv.sh [new file with mode: 0755]
index 729b43058a8afe01b03a463d9c11f5d74d9c8d43..9b88ce9256279728028c2648703ee150ee8a9e0e 100644 (file)
@@ -20,6 +20,7 @@
 #include "mailmap.h"
 #include "parse-options.h"
 #include "utf8.h"
+#include "userdiff.h"
 
 static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
 
@@ -85,17 +86,51 @@ struct origin {
        char path[FLEX_ARRAY];
 };
 
+/*
+ * Prepare diff_filespec and convert it using diff textconv API
+ * if the textconv driver exists.
+ * Return 1 if the conversion succeeds, 0 otherwise.
+ */
+static int textconv_object(const char *path,
+                          const unsigned char *sha1,
+                          char **buf,
+                          unsigned long *buf_size)
+{
+       struct diff_filespec *df;
+       struct userdiff_driver *textconv;
+
+       df = alloc_filespec(path);
+       fill_filespec(df, sha1, S_IFREG | 0664);
+       textconv = get_textconv(df);
+       if (!textconv) {
+               free_filespec(df);
+               return 0;
+       }
+
+       *buf_size = fill_textconv(textconv, df, buf);
+       free_filespec(df);
+       return 1;
+}
+
 /*
  * Given an origin, prepare mmfile_t structure to be used by the
  * diff machinery
  */
-static void fill_origin_blob(struct origin *o, mmfile_t *file)
+static void fill_origin_blob(struct diff_options *opt,
+                            struct origin *o, mmfile_t *file)
 {
        if (!o->file.ptr) {
                enum object_type type;
+               unsigned long file_size;
+
                num_read_blob++;
-               file->ptr = read_sha1_file(o->blob_sha1, &type,
-                                          (unsigned long *)(&(file->size)));
+               if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
+                   textconv_object(o->path, o->blob_sha1, &file->ptr, &file_size))
+                       ;
+               else
+                       file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size);
+               file->size = file_size;
+
                if (!file->ptr)
                        die("Cannot read blob %s for path %s",
                            sha1_to_hex(o->blob_sha1),
@@ -282,7 +317,6 @@ static struct origin *get_origin(struct scoreboard *sb,
 static int fill_blob_sha1(struct origin *origin)
 {
        unsigned mode;
-
        if (!is_null_sha1(origin->blob_sha1))
                return 0;
        if (get_tree_entry(origin->commit->object.sha1,
@@ -742,8 +776,8 @@ static int pass_blame_to_parent(struct scoreboard *sb,
        if (last_in_target < 0)
                return 1; /* nothing remains for this target */
 
-       fill_origin_blob(parent, &file_p);
-       fill_origin_blob(target, &file_o);
+       fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
+       fill_origin_blob(&sb->revs->diffopt, target, &file_o);
        num_get_patch++;
 
        memset(&xpp, 0, sizeof(xpp));
@@ -924,7 +958,7 @@ static int find_move_in_parent(struct scoreboard *sb,
        if (last_in_target < 0)
                return 1; /* nothing remains for this target */
 
-       fill_origin_blob(parent, &file_p);
+       fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
        if (!file_p.ptr)
                return 0;
 
@@ -1065,7 +1099,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
 
                        norigin = get_origin(sb, parent, p->one->path);
                        hashcpy(norigin->blob_sha1, p->one->sha1);
-                       fill_origin_blob(norigin, &file_p);
+                       fill_origin_blob(&sb->revs->diffopt, norigin, &file_p);
                        if (!file_p.ptr)
                                continue;
 
@@ -1985,6 +2019,16 @@ static int git_blame_config(const char *var, const char *value, void *cb)
                blame_date_mode = parse_date_format(value);
                return 0;
        }
+
+       switch (userdiff_config(var, value)) {
+       case 0:
+               break;
+       case -1:
+               return -1;
+       default:
+               return 0;
+       }
+
        return git_default_config(var, value, cb);
 }
 
@@ -1992,7 +2036,9 @@ static int git_blame_config(const char *var, const char *value, void *cb)
  * Prepare a dummy commit that represents the work tree (or staged) item.
  * Note that annotating work tree item never works in the reverse.
  */
-static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
+static struct commit *fake_working_tree_commit(struct diff_options *opt,
+                                              const char *path,
+                                              const char *contents_from)
 {
        struct commit *commit;
        struct origin *origin;
@@ -2020,6 +2066,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
        if (!contents_from || strcmp("-", contents_from)) {
                struct stat st;
                const char *read_from;
+               unsigned long buf_len;
 
                if (contents_from) {
                        if (stat(contents_from, &st) < 0)
@@ -2032,9 +2079,13 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
                        read_from = path;
                }
                mode = canon_mode(st.st_mode);
+
                switch (st.st_mode & S_IFMT) {
                case S_IFREG:
-                       if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
+                       if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
+                           textconv_object(read_from, null_sha1, &buf.buf, &buf_len))
+                               buf.len = buf_len;
+                       else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
                                die_errno("cannot open or read '%s'", read_from);
                        break;
                case S_IFLNK:
@@ -2250,6 +2301,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        git_config(git_blame_config, NULL);
        init_revisions(&revs, NULL);
        revs.date_mode = blame_date_mode;
+       DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
 
        save_commit_buffer = 0;
        dashdash_pos = 0;
@@ -2386,7 +2438,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                 * or "--contents".
                 */
                setup_work_tree();
-               sb.final = fake_working_tree_commit(path, contents_from);
+               sb.final = fake_working_tree_commit(&sb.revs->diffopt,
+                                                   path, contents_from);
                add_pending_object(&revs, &(sb.final->object), ":");
        }
        else if (contents_from)
@@ -2413,8 +2466,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                if (fill_blob_sha1(o))
                        die("no such path %s in %s", path, final_commit_name);
 
-               sb.final_buf = read_sha1_file(o->blob_sha1, &type,
-                                             &sb.final_buf_size);
+               if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) &&
+                   textconv_object(path, o->blob_sha1, (char **) &sb.final_buf,
+                                   &sb.final_buf_size))
+                       ;
+               else
+                       sb.final_buf = read_sha1_file(o->blob_sha1, &type,
+                                                     &sb.final_buf_size);
+
                if (!sb.final_buf)
                        die("Cannot read blob %s for path %s",
                            sha1_to_hex(o->blob_sha1),
diff --git a/diff.c b/diff.c
index c692526603358c189dce213f2d9729a7c26efa94..2332fa6798d9e40e83a07a318675e70d9b3169aa 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -44,10 +44,6 @@ static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_NORMAL,       /* FUNCINFO */
 };
 
-static void diff_filespec_load_driver(struct diff_filespec *one);
-static size_t fill_textconv(struct userdiff_driver *driver,
-                           struct diff_filespec *df, char **outbuf);
-
 static int parse_diff_color_slot(const char *var, int ofs)
 {
        if (!strcasecmp(var+ofs, "plain"))
@@ -1810,7 +1806,7 @@ void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const
                options->b_prefix = b;
 }
 
-static struct userdiff_driver *get_textconv(struct diff_filespec *one)
+struct userdiff_driver *get_textconv(struct diff_filespec *one)
 {
        if (!DIFF_FILE_VALID(one))
                return NULL;
@@ -4243,9 +4239,9 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        return strbuf_detach(&buf, outsize);
 }
 
-static size_t fill_textconv(struct userdiff_driver *driver,
-                           struct diff_filespec *df,
-                           char **outbuf)
+size_t fill_textconv(struct userdiff_driver *driver,
+                    struct diff_filespec *df,
+                    char **outbuf)
 {
        size_t size;
 
diff --git a/diff.h b/diff.h
index 48abe7a96a96df98ef91d5f5d7ba9f57cace777f..063d10ac2216071ef218fab2ad51c13787614acf 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -10,6 +10,8 @@ struct rev_info;
 struct diff_options;
 struct diff_queue_struct;
 struct strbuf;
+struct diff_filespec;
+struct userdiff_driver;
 
 typedef void (*change_fn_t)(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
@@ -74,6 +76,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 #define DIFF_OPT_SUBMODULE_LOG       (1 << 23)
 #define DIFF_OPT_DIRTY_SUBMODULES    (1 << 24)
 #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)
+#define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
 
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
 #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
@@ -292,4 +295,10 @@ extern void diff_no_index(struct rev_info *, int, const char **, int, const char
 
 extern int index_differs_from(const char *def, int diff_flags);
 
+extern size_t fill_textconv(struct userdiff_driver *driver,
+                           struct diff_filespec *df,
+                           char **outbuf);
+
+extern struct userdiff_driver *get_textconv(struct diff_filespec *one);
+
 #endif /* DIFF_H */
diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh
new file mode 100755 (executable)
index 0000000..9ad96d4
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+test_description='git blame textconv support'
+. ./test-lib.sh
+
+find_blame() {
+       sed -e 's/^[^(]*//'
+}
+
+cat >helper <<'EOF'
+#!/bin/sh
+sed 's/^/converted: /' "$@"
+EOF
+chmod +x helper
+
+test_expect_success 'setup ' '
+       echo test 1 >one.bin &&
+       echo test number 2 >two.bin &&
+       git add . &&
+       GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" &&
+       echo test 1 version 2 >one.bin &&
+       echo test number 2 version 2 >>two.bin &&
+       GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00"
+'
+
+cat >expected <<EOF
+(Number2 2010-01-01 20:00:00 +0000 1) test 1 version 2
+EOF
+
+test_expect_success 'no filter specified' '
+       git blame one.bin >blame &&
+       find_blame Number2 <blame >result &&
+       test_cmp expected result
+'
+
+test_expect_success 'setup textconv filters' '
+       echo "*.bin diff=test" >.gitattributes &&
+       git config diff.test.textconv ./helper &&
+       git config diff.test.cachetextconv false
+'
+
+test_expect_success 'blame with --no-textconv' '
+       git blame --no-textconv one.bin >blame &&
+       find_blame <blame> result &&
+       test_cmp expected result
+'
+
+cat >expected <<EOF
+(Number2 2010-01-01 20:00:00 +0000 1) converted: test 1 version 2
+EOF
+
+test_expect_success 'basic blame on last commit' '
+       git blame one.bin >blame &&
+       find_blame  <blame >result &&
+       test_cmp expected result
+'
+
+cat >expected <<EOF
+(Number1 2010-01-01 18:00:00 +0000 1) converted: test number 2
+(Number2 2010-01-01 20:00:00 +0000 2) converted: test number 2 version 2
+EOF
+
+test_expect_success 'blame --textconv going through revisions' '
+       git blame --textconv two.bin >blame &&
+       find_blame <blame >result &&
+       test_cmp expected result
+'
+
+test_expect_success 'make a new commit' '
+       echo "test number 2 version 3" >>two.bin &&
+       GIT_AUTHOR_NAME=Number3 git commit -a -m Third --date="2010-01-01 22:00:00"
+'
+
+test_expect_success 'blame from previous revision' '
+       git blame HEAD^ two.bin >blame &&
+       find_blame <blame >result &&
+       test_cmp expected result
+'
+
+test_done