From a788d7d58bfbd2772b7ad5d788d8f7f885a651e1 Mon Sep 17 00:00:00 2001 From: Axel Bonnet Date: Mon, 7 Jun 2010 17:23:36 +0200 Subject: [PATCH 1/3] textconv: make the API public MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The textconv functionality allows one to convert a file into text before running diff. But this functionality can be useful to other features such as blame. Signed-off-by: Axel Bonnet Signed-off-by: Clément Poulain Signed-off-by: Diane Gasselin Signed-off-by: Junio C Hamano --- diff.c | 12 ++++-------- diff.h | 8 ++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/diff.c b/diff.c index 494f5601e9..b4a830f88f 100644 --- a/diff.c +++ b/diff.c @@ -43,10 +43,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")) @@ -1629,7 +1625,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; @@ -4002,9 +3998,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 9ace08cbae..2a0e36d7a5 100644 --- a/diff.h +++ b/diff.h @@ -9,6 +9,8 @@ struct rev_info; struct diff_options; struct diff_queue_struct; +struct diff_filespec; +struct userdiff_driver; typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, @@ -287,4 +289,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 */ From 3b8a12e8f8c2f4b200e228bc2aa71741c529553c Mon Sep 17 00:00:00 2001 From: Axel Bonnet Date: Tue, 15 Jun 2010 15:58:48 +0200 Subject: [PATCH 2/3] textconv: support for blame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patches enables to perform textconv with blame if a textconv driver is available fos the file. The main task is performed by the textconv_object function which prepares diff_filespec and if possible converts the file using diff textconv API. Only regular files are converted, so the mode of diff_filespec is faked. Textconv conversion is enabled by default (equivalent to the option --textconv), since blaming binary files is useless in most cases. The option --no-textconv is used to disable textconv conversion. The declarations of several functions are modified to give access to a diff_options, in order to know whether the textconv option is activated or not. Signed-off-by: Axel Bonnet Signed-off-by: Clément Poulain Signed-off-by: Diane Gasselin Signed-off-by: Junio C Hamano --- builtin/blame.c | 85 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/builtin/blame.c b/builtin/blame.c index 4dd4c3f494..7bbe7612a1 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -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, @@ -741,8 +775,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)); @@ -922,7 +956,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; @@ -1063,7 +1097,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; @@ -1983,6 +2017,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); } @@ -1990,7 +2034,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; @@ -2018,6 +2064,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) @@ -2030,9 +2077,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: @@ -2248,6 +2299,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; @@ -2384,7 +2436,8 @@ parse_done: * 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) @@ -2411,8 +2464,14 @@ parse_done: 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), From 37d29e1051ef8406d21f8b284a659080fcfe3f72 Mon Sep 17 00:00:00 2001 From: Axel Bonnet Date: Mon, 7 Jun 2010 17:23:38 +0200 Subject: [PATCH 3/3] t/t8006: test textconv support for blame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test the correct functionning of textconv with blame and blame HEAD^ . Test the case when no driver is specified. Signed-off-by: Axel Bonnet Signed-off-by: Clément Poulain Signed-off-by: Diane Gasselin Signed-off-by: Junio C Hamano --- t/t8006-blame-textconv.sh | 80 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100755 t/t8006-blame-textconv.sh diff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh new file mode 100755 index 0000000000..9ad96d4d32 --- /dev/null +++ b/t/t8006-blame-textconv.sh @@ -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 <blame && + find_blame Number2 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 result && + test_cmp expected result +' + +cat >expected <blame && + find_blame result && + test_cmp expected result +' + +cat >expected <blame && + find_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 result && + test_cmp expected result +' + +test_done