зеркало из https://github.com/microsoft/git.git
Merge branch 'ab/blame-textconv'
* ab/blame-textconv: t/t8006: test textconv support for blame textconv: support for blame textconv: make the API public Conflicts: diff.h
This commit is contained in:
Коммит
4af574dbdc
|
@ -20,6 +20,7 @@
|
||||||
#include "mailmap.h"
|
#include "mailmap.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
|
#include "userdiff.h"
|
||||||
|
|
||||||
static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
|
static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
|
||||||
|
|
||||||
|
@ -85,17 +86,51 @@ struct origin {
|
||||||
char path[FLEX_ARRAY];
|
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
|
* Given an origin, prepare mmfile_t structure to be used by the
|
||||||
* diff machinery
|
* 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) {
|
if (!o->file.ptr) {
|
||||||
enum object_type type;
|
enum object_type type;
|
||||||
|
unsigned long file_size;
|
||||||
|
|
||||||
num_read_blob++;
|
num_read_blob++;
|
||||||
file->ptr = read_sha1_file(o->blob_sha1, &type,
|
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
|
||||||
(unsigned long *)(&(file->size)));
|
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)
|
if (!file->ptr)
|
||||||
die("Cannot read blob %s for path %s",
|
die("Cannot read blob %s for path %s",
|
||||||
sha1_to_hex(o->blob_sha1),
|
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)
|
static int fill_blob_sha1(struct origin *origin)
|
||||||
{
|
{
|
||||||
unsigned mode;
|
unsigned mode;
|
||||||
|
|
||||||
if (!is_null_sha1(origin->blob_sha1))
|
if (!is_null_sha1(origin->blob_sha1))
|
||||||
return 0;
|
return 0;
|
||||||
if (get_tree_entry(origin->commit->object.sha1,
|
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)
|
if (last_in_target < 0)
|
||||||
return 1; /* nothing remains for this target */
|
return 1; /* nothing remains for this target */
|
||||||
|
|
||||||
fill_origin_blob(parent, &file_p);
|
fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
|
||||||
fill_origin_blob(target, &file_o);
|
fill_origin_blob(&sb->revs->diffopt, target, &file_o);
|
||||||
num_get_patch++;
|
num_get_patch++;
|
||||||
|
|
||||||
memset(&xpp, 0, sizeof(xpp));
|
memset(&xpp, 0, sizeof(xpp));
|
||||||
|
@ -924,7 +958,7 @@ static int find_move_in_parent(struct scoreboard *sb,
|
||||||
if (last_in_target < 0)
|
if (last_in_target < 0)
|
||||||
return 1; /* nothing remains for this target */
|
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)
|
if (!file_p.ptr)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -1065,7 +1099,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
|
||||||
|
|
||||||
norigin = get_origin(sb, parent, p->one->path);
|
norigin = get_origin(sb, parent, p->one->path);
|
||||||
hashcpy(norigin->blob_sha1, p->one->sha1);
|
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)
|
if (!file_p.ptr)
|
||||||
continue;
|
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);
|
blame_date_mode = parse_date_format(value);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (userdiff_config(var, value)) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case -1:
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return git_default_config(var, value, cb);
|
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.
|
* Prepare a dummy commit that represents the work tree (or staged) item.
|
||||||
* Note that annotating work tree item never works in the reverse.
|
* 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 commit *commit;
|
||||||
struct origin *origin;
|
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)) {
|
if (!contents_from || strcmp("-", contents_from)) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
const char *read_from;
|
const char *read_from;
|
||||||
|
unsigned long buf_len;
|
||||||
|
|
||||||
if (contents_from) {
|
if (contents_from) {
|
||||||
if (stat(contents_from, &st) < 0)
|
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;
|
read_from = path;
|
||||||
}
|
}
|
||||||
mode = canon_mode(st.st_mode);
|
mode = canon_mode(st.st_mode);
|
||||||
|
|
||||||
switch (st.st_mode & S_IFMT) {
|
switch (st.st_mode & S_IFMT) {
|
||||||
case S_IFREG:
|
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);
|
die_errno("cannot open or read '%s'", read_from);
|
||||||
break;
|
break;
|
||||||
case S_IFLNK:
|
case S_IFLNK:
|
||||||
|
@ -2250,6 +2301,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
|
||||||
git_config(git_blame_config, NULL);
|
git_config(git_blame_config, NULL);
|
||||||
init_revisions(&revs, NULL);
|
init_revisions(&revs, NULL);
|
||||||
revs.date_mode = blame_date_mode;
|
revs.date_mode = blame_date_mode;
|
||||||
|
DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
|
||||||
|
|
||||||
save_commit_buffer = 0;
|
save_commit_buffer = 0;
|
||||||
dashdash_pos = 0;
|
dashdash_pos = 0;
|
||||||
|
@ -2386,7 +2438,8 @@ parse_done:
|
||||||
* or "--contents".
|
* or "--contents".
|
||||||
*/
|
*/
|
||||||
setup_work_tree();
|
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), ":");
|
add_pending_object(&revs, &(sb.final->object), ":");
|
||||||
}
|
}
|
||||||
else if (contents_from)
|
else if (contents_from)
|
||||||
|
@ -2413,8 +2466,14 @@ parse_done:
|
||||||
if (fill_blob_sha1(o))
|
if (fill_blob_sha1(o))
|
||||||
die("no such path %s in %s", path, final_commit_name);
|
die("no such path %s in %s", path, final_commit_name);
|
||||||
|
|
||||||
sb.final_buf = read_sha1_file(o->blob_sha1, &type,
|
if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) &&
|
||||||
&sb.final_buf_size);
|
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)
|
if (!sb.final_buf)
|
||||||
die("Cannot read blob %s for path %s",
|
die("Cannot read blob %s for path %s",
|
||||||
sha1_to_hex(o->blob_sha1),
|
sha1_to_hex(o->blob_sha1),
|
||||||
|
|
12
diff.c
12
diff.c
|
@ -44,10 +44,6 @@ static char diff_colors[][COLOR_MAXLEN] = {
|
||||||
GIT_COLOR_NORMAL, /* FUNCINFO */
|
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)
|
static int parse_diff_color_slot(const char *var, int ofs)
|
||||||
{
|
{
|
||||||
if (!strcasecmp(var+ofs, "plain"))
|
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;
|
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))
|
if (!DIFF_FILE_VALID(one))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -4243,9 +4239,9 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
|
||||||
return strbuf_detach(&buf, outsize);
|
return strbuf_detach(&buf, outsize);
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t fill_textconv(struct userdiff_driver *driver,
|
size_t fill_textconv(struct userdiff_driver *driver,
|
||||||
struct diff_filespec *df,
|
struct diff_filespec *df,
|
||||||
char **outbuf)
|
char **outbuf)
|
||||||
{
|
{
|
||||||
size_t size;
|
size_t size;
|
||||||
|
|
||||||
|
|
9
diff.h
9
diff.h
|
@ -10,6 +10,8 @@ struct rev_info;
|
||||||
struct diff_options;
|
struct diff_options;
|
||||||
struct diff_queue_struct;
|
struct diff_queue_struct;
|
||||||
struct strbuf;
|
struct strbuf;
|
||||||
|
struct diff_filespec;
|
||||||
|
struct userdiff_driver;
|
||||||
|
|
||||||
typedef void (*change_fn_t)(struct diff_options *options,
|
typedef void (*change_fn_t)(struct diff_options *options,
|
||||||
unsigned old_mode, unsigned new_mode,
|
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_SUBMODULE_LOG (1 << 23)
|
||||||
#define DIFF_OPT_DIRTY_SUBMODULES (1 << 24)
|
#define DIFF_OPT_DIRTY_SUBMODULES (1 << 24)
|
||||||
#define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)
|
#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_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag)
|
||||||
#define DIFF_OPT_SET(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 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 */
|
#endif /* DIFF_H */
|
||||||
|
|
|
@ -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
|
Загрузка…
Ссылка в новой задаче