Merge branch 'br/blame-ignore'
"git blame" learned to "ignore" commits in the history, whose effects (as well as their presence) get ignored. * br/blame-ignore: t8014: remove unnecessary braces blame: drop some unused function parameters blame: add a test to cover blame_coalesce() blame: use the fingerprint heuristic to match ignored lines blame: add a fingerprint heuristic to match ignored lines blame: optionally track line fingerprints during fill_blame_origin() blame: add config options for the output of ignored or unblamable lines blame: add the ability to ignore commits and their changes blame: use a helper function in blame_chunk() Move oidset_parse_file() to oidset.c fsck: rename and touch up init_skiplist()
This commit is contained in:
commit
209f075593
@ -110,5 +110,24 @@ commit. And the default value is 40. If there are more than one
|
|||||||
`-C` options given, the <num> argument of the last `-C` will
|
`-C` options given, the <num> argument of the last `-C` will
|
||||||
take effect.
|
take effect.
|
||||||
|
|
||||||
|
--ignore-rev <rev>::
|
||||||
|
Ignore changes made by the revision when assigning blame, as if the
|
||||||
|
change never happened. Lines that were changed or added by an ignored
|
||||||
|
commit will be blamed on the previous commit that changed that line or
|
||||||
|
nearby lines. This option may be specified multiple times to ignore
|
||||||
|
more than one revision. If the `blame.markIgnoredLines` config option
|
||||||
|
is set, then lines that were changed by an ignored commit and attributed to
|
||||||
|
another commit will be marked with a `?` in the blame output. If the
|
||||||
|
`blame.markUnblamableLines` config option is set, then those lines touched
|
||||||
|
by an ignored commit that we could not attribute to another revision are
|
||||||
|
marked with a '*'.
|
||||||
|
|
||||||
|
--ignore-revs-file <file>::
|
||||||
|
Ignore revisions listed in `file`, which must be in the same format as an
|
||||||
|
`fsck.skipList`. This option may be repeated, and these files will be
|
||||||
|
processed after any files specified with the `blame.ignoreRevsFile` config
|
||||||
|
option. An empty file name, `""`, will clear the list of revs from
|
||||||
|
previously processed files.
|
||||||
|
|
||||||
-h::
|
-h::
|
||||||
Show help message.
|
Show help message.
|
||||||
|
@ -19,3 +19,19 @@ blame.showEmail::
|
|||||||
blame.showRoot::
|
blame.showRoot::
|
||||||
Do not treat root commits as boundaries in linkgit:git-blame[1].
|
Do not treat root commits as boundaries in linkgit:git-blame[1].
|
||||||
This option defaults to false.
|
This option defaults to false.
|
||||||
|
|
||||||
|
blame.ignoreRevsFile::
|
||||||
|
Ignore revisions listed in the file, one unabbreviated object name per
|
||||||
|
line, in linkgit:git-blame[1]. Whitespace and comments beginning with
|
||||||
|
`#` are ignored. This option may be repeated multiple times. Empty
|
||||||
|
file names will reset the list of ignored revisions. This option will
|
||||||
|
be handled before the command line option `--ignore-revs-file`.
|
||||||
|
|
||||||
|
blame.markUnblamables::
|
||||||
|
Mark lines that were changed by an ignored revision that we could not
|
||||||
|
attribute to another commit with a '*' in the output of
|
||||||
|
linkgit:git-blame[1].
|
||||||
|
|
||||||
|
blame.markIgnoredLines::
|
||||||
|
Mark lines that were changed by an ignored revision that we attributed to
|
||||||
|
another commit with a '?' in the output of linkgit:git-blame[1].
|
||||||
|
@ -10,6 +10,7 @@ SYNOPSIS
|
|||||||
[verse]
|
[verse]
|
||||||
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
|
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
|
||||||
[-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
|
[-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
|
||||||
|
[--ignore-rev <rev>] [--ignore-revs-file <file>]
|
||||||
[--progress] [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>..<rev>]
|
[--progress] [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>..<rev>]
|
||||||
[--] <file>
|
[--] <file>
|
||||||
|
|
||||||
|
6
blame.h
6
blame.h
@ -51,6 +51,8 @@ struct blame_origin {
|
|||||||
*/
|
*/
|
||||||
struct blame_entry *suspects;
|
struct blame_entry *suspects;
|
||||||
mmfile_t file;
|
mmfile_t file;
|
||||||
|
int num_lines;
|
||||||
|
void *fingerprints;
|
||||||
struct object_id blob_oid;
|
struct object_id blob_oid;
|
||||||
unsigned short mode;
|
unsigned short mode;
|
||||||
/* guilty gets set when shipping any suspects to the final
|
/* guilty gets set when shipping any suspects to the final
|
||||||
@ -92,6 +94,8 @@ struct blame_entry {
|
|||||||
* scanning the lines over and over.
|
* scanning the lines over and over.
|
||||||
*/
|
*/
|
||||||
unsigned score;
|
unsigned score;
|
||||||
|
int ignored;
|
||||||
|
int unblamable;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -117,6 +121,8 @@ struct blame_scoreboard {
|
|||||||
/* linked list of blames */
|
/* linked list of blames */
|
||||||
struct blame_entry *ent;
|
struct blame_entry *ent;
|
||||||
|
|
||||||
|
struct oidset ignore_list;
|
||||||
|
|
||||||
/* look-up a line in the final buffer */
|
/* look-up a line in the final buffer */
|
||||||
int num_lines;
|
int num_lines;
|
||||||
int *lineno;
|
int *lineno;
|
||||||
|
@ -53,6 +53,9 @@ static int no_whole_file_rename;
|
|||||||
static int show_progress;
|
static int show_progress;
|
||||||
static char repeated_meta_color[COLOR_MAXLEN];
|
static char repeated_meta_color[COLOR_MAXLEN];
|
||||||
static int coloring_mode;
|
static int coloring_mode;
|
||||||
|
static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP;
|
||||||
|
static int mark_unblamable_lines;
|
||||||
|
static int mark_ignored_lines;
|
||||||
|
|
||||||
static struct date_mode blame_date_mode = { DATE_ISO8601 };
|
static struct date_mode blame_date_mode = { DATE_ISO8601 };
|
||||||
static size_t blame_date_width;
|
static size_t blame_date_width;
|
||||||
@ -480,6 +483,14 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mark_unblamable_lines && ent->unblamable) {
|
||||||
|
length--;
|
||||||
|
putchar('*');
|
||||||
|
}
|
||||||
|
if (mark_ignored_lines && ent->ignored) {
|
||||||
|
length--;
|
||||||
|
putchar('?');
|
||||||
|
}
|
||||||
printf("%.*s", length, hex);
|
printf("%.*s", length, hex);
|
||||||
if (opt & OUTPUT_ANNOTATE_COMPAT) {
|
if (opt & OUTPUT_ANNOTATE_COMPAT) {
|
||||||
const char *name;
|
const char *name;
|
||||||
@ -696,6 +707,24 @@ static int git_blame_config(const char *var, const char *value, void *cb)
|
|||||||
parse_date_format(value, &blame_date_mode);
|
parse_date_format(value, &blame_date_mode);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (!strcmp(var, "blame.ignorerevsfile")) {
|
||||||
|
const char *str;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = git_config_pathname(&str, var, value);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
string_list_insert(&ignore_revs_file_list, str);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!strcmp(var, "blame.markunblamablelines")) {
|
||||||
|
mark_unblamable_lines = git_config_bool(var, value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!strcmp(var, "blame.markignoredlines")) {
|
||||||
|
mark_ignored_lines = git_config_bool(var, value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (!strcmp(var, "color.blame.repeatedlines")) {
|
if (!strcmp(var, "color.blame.repeatedlines")) {
|
||||||
if (color_parse_mem(value, strlen(value), repeated_meta_color))
|
if (color_parse_mem(value, strlen(value), repeated_meta_color))
|
||||||
warning(_("invalid color '%s' in color.blame.repeatedLines"),
|
warning(_("invalid color '%s' in color.blame.repeatedLines"),
|
||||||
@ -775,6 +804,27 @@ static int is_a_rev(const char *name)
|
|||||||
return OBJ_NONE < oid_object_info(the_repository, &oid, NULL);
|
return OBJ_NONE < oid_object_info(the_repository, &oid, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void build_ignorelist(struct blame_scoreboard *sb,
|
||||||
|
struct string_list *ignore_revs_file_list,
|
||||||
|
struct string_list *ignore_rev_list)
|
||||||
|
{
|
||||||
|
struct string_list_item *i;
|
||||||
|
struct object_id oid;
|
||||||
|
|
||||||
|
oidset_init(&sb->ignore_list, 0);
|
||||||
|
for_each_string_list_item(i, ignore_revs_file_list) {
|
||||||
|
if (!strcmp(i->string, ""))
|
||||||
|
oidset_clear(&sb->ignore_list);
|
||||||
|
else
|
||||||
|
oidset_parse_file(&sb->ignore_list, i->string);
|
||||||
|
}
|
||||||
|
for_each_string_list_item(i, ignore_rev_list) {
|
||||||
|
if (get_oid_committish(i->string, &oid))
|
||||||
|
die(_("cannot find revision %s to ignore"), i->string);
|
||||||
|
oidset_insert(&sb->ignore_list, &oid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int cmd_blame(int argc, const char **argv, const char *prefix)
|
int cmd_blame(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
struct rev_info revs;
|
struct rev_info revs;
|
||||||
@ -786,6 +836,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
|
|||||||
struct progress_info pi = { NULL, 0 };
|
struct progress_info pi = { NULL, 0 };
|
||||||
|
|
||||||
struct string_list range_list = STRING_LIST_INIT_NODUP;
|
struct string_list range_list = STRING_LIST_INIT_NODUP;
|
||||||
|
struct string_list ignore_rev_list = STRING_LIST_INIT_NODUP;
|
||||||
int output_option = 0, opt = 0;
|
int output_option = 0, opt = 0;
|
||||||
int show_stats = 0;
|
int show_stats = 0;
|
||||||
const char *revs_file = NULL;
|
const char *revs_file = NULL;
|
||||||
@ -807,6 +858,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
|
|||||||
OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
|
OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
|
||||||
OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
|
OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
|
||||||
OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
|
OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
|
||||||
|
OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("Ignore <rev> when blaming")),
|
||||||
|
OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from <file>")),
|
||||||
OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE),
|
OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE),
|
||||||
OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR),
|
OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR),
|
||||||
|
|
||||||
@ -1012,6 +1065,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
|
|||||||
sb.contents_from = contents_from;
|
sb.contents_from = contents_from;
|
||||||
sb.reverse = reverse;
|
sb.reverse = reverse;
|
||||||
sb.repo = the_repository;
|
sb.repo = the_repository;
|
||||||
|
build_ignorelist(&sb, &ignore_revs_file_list, &ignore_rev_list);
|
||||||
|
string_list_clear(&ignore_revs_file_list, 0);
|
||||||
|
string_list_clear(&ignore_rev_list, 0);
|
||||||
setup_scoreboard(&sb, path, &o);
|
setup_scoreboard(&sb, path, &o);
|
||||||
lno = sb.num_lines;
|
lno = sb.num_lines;
|
||||||
|
|
||||||
|
37
fsck.c
37
fsck.c
@ -181,41 +181,6 @@ static int fsck_msg_type(enum fsck_msg_id msg_id,
|
|||||||
return msg_type;
|
return msg_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void init_skiplist(struct fsck_options *options, const char *path)
|
|
||||||
{
|
|
||||||
FILE *fp;
|
|
||||||
struct strbuf sb = STRBUF_INIT;
|
|
||||||
struct object_id oid;
|
|
||||||
|
|
||||||
fp = fopen(path, "r");
|
|
||||||
if (!fp)
|
|
||||||
die("Could not open skip list: %s", path);
|
|
||||||
while (!strbuf_getline(&sb, fp)) {
|
|
||||||
const char *p;
|
|
||||||
const char *hash;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Allow trailing comments, leading whitespace
|
|
||||||
* (including before commits), and empty or whitespace
|
|
||||||
* only lines.
|
|
||||||
*/
|
|
||||||
hash = strchr(sb.buf, '#');
|
|
||||||
if (hash)
|
|
||||||
strbuf_setlen(&sb, hash - sb.buf);
|
|
||||||
strbuf_trim(&sb);
|
|
||||||
if (!sb.len)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (parse_oid_hex(sb.buf, &oid, &p) || *p != '\0')
|
|
||||||
die("Invalid SHA-1: %s", sb.buf);
|
|
||||||
oidset_insert(&options->skiplist, &oid);
|
|
||||||
}
|
|
||||||
if (ferror(fp))
|
|
||||||
die_errno("Could not read '%s'", path);
|
|
||||||
fclose(fp);
|
|
||||||
strbuf_release(&sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_msg_type(const char *str)
|
static int parse_msg_type(const char *str)
|
||||||
{
|
{
|
||||||
if (!strcmp(str, "error"))
|
if (!strcmp(str, "error"))
|
||||||
@ -284,7 +249,7 @@ void fsck_set_msg_types(struct fsck_options *options, const char *values)
|
|||||||
if (!strcmp(buf, "skiplist")) {
|
if (!strcmp(buf, "skiplist")) {
|
||||||
if (equal == len)
|
if (equal == len)
|
||||||
die("skiplist requires a path");
|
die("skiplist requires a path");
|
||||||
init_skiplist(options, buf + equal + 1);
|
oidset_parse_file(&options->skiplist, buf + equal + 1);
|
||||||
buf += len + 1;
|
buf += len + 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
35
oidset.c
35
oidset.c
@ -35,3 +35,38 @@ void oidset_clear(struct oidset *set)
|
|||||||
kh_release_oid_set(&set->set);
|
kh_release_oid_set(&set->set);
|
||||||
oidset_init(set, 0);
|
oidset_init(set, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void oidset_parse_file(struct oidset *set, const char *path)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
struct strbuf sb = STRBUF_INIT;
|
||||||
|
struct object_id oid;
|
||||||
|
|
||||||
|
fp = fopen(path, "r");
|
||||||
|
if (!fp)
|
||||||
|
die("could not open object name list: %s", path);
|
||||||
|
while (!strbuf_getline(&sb, fp)) {
|
||||||
|
const char *p;
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allow trailing comments, leading whitespace
|
||||||
|
* (including before commits), and empty or whitespace
|
||||||
|
* only lines.
|
||||||
|
*/
|
||||||
|
name = strchr(sb.buf, '#');
|
||||||
|
if (name)
|
||||||
|
strbuf_setlen(&sb, name - sb.buf);
|
||||||
|
strbuf_trim(&sb);
|
||||||
|
if (!sb.len)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (parse_oid_hex(sb.buf, &oid, &p) || *p != '\0')
|
||||||
|
die("invalid object name: %s", sb.buf);
|
||||||
|
oidset_insert(set, &oid);
|
||||||
|
}
|
||||||
|
if (ferror(fp))
|
||||||
|
die_errno("Could not read '%s'", path);
|
||||||
|
fclose(fp);
|
||||||
|
strbuf_release(&sb);
|
||||||
|
}
|
||||||
|
8
oidset.h
8
oidset.h
@ -61,6 +61,14 @@ int oidset_remove(struct oidset *set, const struct object_id *oid);
|
|||||||
*/
|
*/
|
||||||
void oidset_clear(struct oidset *set);
|
void oidset_clear(struct oidset *set);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the contents of the file 'path' to an initialized oidset. Each line is
|
||||||
|
* an unabbreviated object name. Comments begin with '#', and trailing comments
|
||||||
|
* are allowed. Leading whitespace and empty or white-space only lines are
|
||||||
|
* ignored.
|
||||||
|
*/
|
||||||
|
void oidset_parse_file(struct oidset *set, const char *path);
|
||||||
|
|
||||||
struct oidset_iter {
|
struct oidset_iter {
|
||||||
kh_oid_set_t *set;
|
kh_oid_set_t *set;
|
||||||
khiter_t iter;
|
khiter_t iter;
|
||||||
|
@ -164,9 +164,9 @@ test_expect_success 'fsck with unsorted skipList' '
|
|||||||
test_expect_success 'fsck with invalid or bogus skipList input' '
|
test_expect_success 'fsck with invalid or bogus skipList input' '
|
||||||
git -c fsck.skipList=/dev/null -c fsck.missingEmail=ignore fsck &&
|
git -c fsck.skipList=/dev/null -c fsck.missingEmail=ignore fsck &&
|
||||||
test_must_fail git -c fsck.skipList=does-not-exist -c fsck.missingEmail=ignore fsck 2>err &&
|
test_must_fail git -c fsck.skipList=does-not-exist -c fsck.missingEmail=ignore fsck 2>err &&
|
||||||
test_i18ngrep "Could not open skip list: does-not-exist" err &&
|
test_i18ngrep "could not open.*: does-not-exist" err &&
|
||||||
test_must_fail git -c fsck.skipList=.git/config -c fsck.missingEmail=ignore fsck 2>err &&
|
test_must_fail git -c fsck.skipList=.git/config -c fsck.missingEmail=ignore fsck 2>err &&
|
||||||
test_i18ngrep "Invalid SHA-1: \[core\]" err
|
test_i18ngrep "invalid object name: \[core\]" err
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'fsck with other accepted skipList input (comments & empty lines)' '
|
test_expect_success 'fsck with other accepted skipList input (comments & empty lines)' '
|
||||||
@ -193,7 +193,7 @@ test_expect_success 'fsck no garbage output from comments & empty lines errors'
|
|||||||
test_expect_success 'fsck with invalid abbreviated skipList input' '
|
test_expect_success 'fsck with invalid abbreviated skipList input' '
|
||||||
echo $commit | test_copy_bytes 20 >SKIP.abbreviated &&
|
echo $commit | test_copy_bytes 20 >SKIP.abbreviated &&
|
||||||
test_must_fail git -c fsck.skipList=SKIP.abbreviated fsck 2>err-abbreviated &&
|
test_must_fail git -c fsck.skipList=SKIP.abbreviated fsck 2>err-abbreviated &&
|
||||||
test_i18ngrep "^fatal: Invalid SHA-1: " err-abbreviated
|
test_i18ngrep "^fatal: invalid object name: " err-abbreviated
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'fsck with exhaustive accepted skipList input (various types of comments etc.)' '
|
test_expect_success 'fsck with exhaustive accepted skipList input (various types of comments etc.)' '
|
||||||
@ -226,10 +226,10 @@ test_expect_success 'push with receive.fsck.skipList' '
|
|||||||
test_must_fail git push --porcelain dst bogus &&
|
test_must_fail git push --porcelain dst bogus &&
|
||||||
git --git-dir=dst/.git config receive.fsck.skipList does-not-exist &&
|
git --git-dir=dst/.git config receive.fsck.skipList does-not-exist &&
|
||||||
test_must_fail git push --porcelain dst bogus 2>err &&
|
test_must_fail git push --porcelain dst bogus 2>err &&
|
||||||
test_i18ngrep "Could not open skip list: does-not-exist" err &&
|
test_i18ngrep "could not open.*: does-not-exist" err &&
|
||||||
git --git-dir=dst/.git config receive.fsck.skipList config &&
|
git --git-dir=dst/.git config receive.fsck.skipList config &&
|
||||||
test_must_fail git push --porcelain dst bogus 2>err &&
|
test_must_fail git push --porcelain dst bogus 2>err &&
|
||||||
test_i18ngrep "Invalid SHA-1: \[core\]" err &&
|
test_i18ngrep "invalid object name: \[core\]" err &&
|
||||||
|
|
||||||
git --git-dir=dst/.git config receive.fsck.skipList SKIP &&
|
git --git-dir=dst/.git config receive.fsck.skipList SKIP &&
|
||||||
git push --porcelain dst bogus
|
git push --porcelain dst bogus
|
||||||
@ -255,10 +255,10 @@ test_expect_success 'fetch with fetch.fsck.skipList' '
|
|||||||
test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec &&
|
test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec &&
|
||||||
git --git-dir=dst/.git config fetch.fsck.skipList does-not-exist &&
|
git --git-dir=dst/.git config fetch.fsck.skipList does-not-exist &&
|
||||||
test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec 2>err &&
|
test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec 2>err &&
|
||||||
test_i18ngrep "Could not open skip list: does-not-exist" err &&
|
test_i18ngrep "could not open.*: does-not-exist" err &&
|
||||||
git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/config &&
|
git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/config &&
|
||||||
test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec 2>err &&
|
test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec 2>err &&
|
||||||
test_i18ngrep "Invalid SHA-1: \[core\]" err &&
|
test_i18ngrep "invalid object name: \[core\]" err &&
|
||||||
|
|
||||||
git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/SKIP &&
|
git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/SKIP &&
|
||||||
git --git-dir=dst/.git fetch "file://$(pwd)" $refspec
|
git --git-dir=dst/.git fetch "file://$(pwd)" $refspec
|
||||||
|
@ -275,4 +275,40 @@ test_expect_success 'blame file with CRLF core.autocrlf=true' '
|
|||||||
grep "A U Thor" actual
|
grep "A U Thor" actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# Tests the splitting and merging of blame entries in blame_coalesce().
|
||||||
|
# The output of blame is the same, regardless of whether blame_coalesce() runs
|
||||||
|
# or not, so we'd likely only notice a problem if blame crashes or assigned
|
||||||
|
# blame to the "splitting" commit ('SPLIT' below).
|
||||||
|
test_expect_success 'blame coalesce' '
|
||||||
|
cat >giraffe <<-\EOF &&
|
||||||
|
ABC
|
||||||
|
DEF
|
||||||
|
EOF
|
||||||
|
git add giraffe &&
|
||||||
|
git commit -m "original file" &&
|
||||||
|
oid=$(git rev-parse HEAD) &&
|
||||||
|
|
||||||
|
cat >giraffe <<-\EOF &&
|
||||||
|
ABC
|
||||||
|
SPLIT
|
||||||
|
DEF
|
||||||
|
EOF
|
||||||
|
git add giraffe &&
|
||||||
|
git commit -m "interior SPLIT line" &&
|
||||||
|
|
||||||
|
cat >giraffe <<-\EOF &&
|
||||||
|
ABC
|
||||||
|
DEF
|
||||||
|
EOF
|
||||||
|
git add giraffe &&
|
||||||
|
git commit -m "same contents as original" &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$oid 1) ABC
|
||||||
|
$oid 2) DEF
|
||||||
|
EOF
|
||||||
|
git -c core.abbrev=40 blame -s giraffe >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
274
t/t8013-blame-ignore-revs.sh
Executable file
274
t/t8013-blame-ignore-revs.sh
Executable file
@ -0,0 +1,274 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='ignore revisions when blaming'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
# Creates:
|
||||||
|
# A--B--X
|
||||||
|
# A added line 1 and B added line 2. X makes changes to those lines. Sanity
|
||||||
|
# check that X is blamed for both lines.
|
||||||
|
test_expect_success setup '
|
||||||
|
test_commit A file line1 &&
|
||||||
|
|
||||||
|
echo line2 >>file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m B &&
|
||||||
|
git tag B &&
|
||||||
|
|
||||||
|
test_write_lines line-one line-two >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m X &&
|
||||||
|
git tag X &&
|
||||||
|
|
||||||
|
git blame --line-porcelain file >blame_raw &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
git rev-parse X >expect &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
git rev-parse X >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# Ignore X, make sure A is blamed for line 1 and B for line 2.
|
||||||
|
test_expect_success ignore_rev_changing_lines '
|
||||||
|
git blame --line-porcelain --ignore-rev X file >blame_raw &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
git rev-parse A >expect &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
git rev-parse B >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# For ignored revs that have added 'unblamable' lines, attribute those to the
|
||||||
|
# ignored commit.
|
||||||
|
# A--B--X--Y
|
||||||
|
# Where Y changes lines 1 and 2, and adds lines 3 and 4. The added lines ought
|
||||||
|
# to have nothing in common with "line-one" or "line-two", to keep any
|
||||||
|
# heuristics from matching them with any lines in the parent.
|
||||||
|
test_expect_success ignore_rev_adding_unblamable_lines '
|
||||||
|
test_write_lines line-one-change line-two-changed y3 y4 >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m Y &&
|
||||||
|
git tag Y &&
|
||||||
|
|
||||||
|
git rev-parse Y >expect &&
|
||||||
|
git blame --line-porcelain file --ignore-rev Y >blame_raw &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 3" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 4" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# Ignore X and Y, both in separate files. Lines 1 == A, 2 == B.
|
||||||
|
test_expect_success ignore_revs_from_files '
|
||||||
|
git rev-parse X >ignore_x &&
|
||||||
|
git rev-parse Y >ignore_y &&
|
||||||
|
git blame --line-porcelain file --ignore-revs-file ignore_x --ignore-revs-file ignore_y >blame_raw &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
git rev-parse A >expect &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
git rev-parse B >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# Ignore X from the config option, Y from a file.
|
||||||
|
test_expect_success ignore_revs_from_configs_and_files '
|
||||||
|
git config --add blame.ignoreRevsFile ignore_x &&
|
||||||
|
git blame --line-porcelain file --ignore-revs-file ignore_y >blame_raw &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
git rev-parse A >expect &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
git rev-parse B >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# Override blame.ignoreRevsFile (ignore_x) with an empty string. X should be
|
||||||
|
# blamed now for lines 1 and 2, since we are no longer ignoring X.
|
||||||
|
test_expect_success override_ignore_revs_file '
|
||||||
|
git blame --line-porcelain file --ignore-revs-file "" --ignore-revs-file ignore_y >blame_raw &&
|
||||||
|
git rev-parse X >expect &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
test_expect_success bad_files_and_revs '
|
||||||
|
test_must_fail git blame file --ignore-rev NOREV 2>err &&
|
||||||
|
test_i18ngrep "cannot find revision NOREV to ignore" err &&
|
||||||
|
|
||||||
|
test_must_fail git blame file --ignore-revs-file NOFILE 2>err &&
|
||||||
|
test_i18ngrep "could not open.*: NOFILE" err &&
|
||||||
|
|
||||||
|
echo NOREV >ignore_norev &&
|
||||||
|
test_must_fail git blame file --ignore-revs-file ignore_norev 2>err &&
|
||||||
|
test_i18ngrep "invalid object name: NOREV" err
|
||||||
|
'
|
||||||
|
|
||||||
|
# For ignored revs that have added 'unblamable' lines, mark those lines with a
|
||||||
|
# '*'
|
||||||
|
# A--B--X--Y
|
||||||
|
# Lines 3 and 4 are from Y and unblamable. This was set up in
|
||||||
|
# ignore_rev_adding_unblamable_lines.
|
||||||
|
test_expect_success mark_unblamable_lines '
|
||||||
|
git config --add blame.markUnblamableLines true &&
|
||||||
|
|
||||||
|
git blame --ignore-rev Y file >blame_raw &&
|
||||||
|
echo "*" >expect &&
|
||||||
|
|
||||||
|
sed -n "3p" blame_raw | cut -c1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
sed -n "4p" blame_raw | cut -c1 >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# Commit Z will touch the first two lines. Y touched all four.
|
||||||
|
# A--B--X--Y--Z
|
||||||
|
# The blame output when ignoring Z should be:
|
||||||
|
# ?Y ... 1)
|
||||||
|
# ?Y ... 2)
|
||||||
|
# Y ... 3)
|
||||||
|
# Y ... 4)
|
||||||
|
# We're checking only the first character
|
||||||
|
test_expect_success mark_ignored_lines '
|
||||||
|
git config --add blame.markIgnoredLines true &&
|
||||||
|
|
||||||
|
test_write_lines line-one-Z line-two-Z y3 y4 >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m Z &&
|
||||||
|
git tag Z &&
|
||||||
|
|
||||||
|
git blame --ignore-rev Z file >blame_raw &&
|
||||||
|
echo "?" >expect &&
|
||||||
|
|
||||||
|
sed -n "1p" blame_raw | cut -c1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
sed -n "2p" blame_raw | cut -c1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
sed -n "3p" blame_raw | cut -c1 >actual &&
|
||||||
|
! test_cmp expect actual &&
|
||||||
|
|
||||||
|
sed -n "4p" blame_raw | cut -c1 >actual &&
|
||||||
|
! test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# For ignored revs that added 'unblamable' lines and more recent commits changed
|
||||||
|
# the blamable lines, mark the unblamable lines with a
|
||||||
|
# '*'
|
||||||
|
# A--B--X--Y--Z
|
||||||
|
# Lines 3 and 4 are from Y and unblamable, as set up in
|
||||||
|
# ignore_rev_adding_unblamable_lines. Z changed lines 1 and 2.
|
||||||
|
test_expect_success mark_unblamable_lines_intermediate '
|
||||||
|
git config --add blame.markUnblamableLines true &&
|
||||||
|
|
||||||
|
git blame --ignore-rev Y file >blame_raw 2>stderr &&
|
||||||
|
echo "*" >expect &&
|
||||||
|
|
||||||
|
sed -n "3p" blame_raw | cut -c1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
sed -n "4p" blame_raw | cut -c1 >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# The heuristic called by guess_line_blames() tries to find the size of a
|
||||||
|
# blame_entry 'e' in the parent's address space. Those calculations need to
|
||||||
|
# check for negative or zero values for when a blame entry is completely outside
|
||||||
|
# the window of the parent's version of a file.
|
||||||
|
#
|
||||||
|
# This happens when one commit adds several lines (commit B below). A later
|
||||||
|
# commit (C) changes one line in the middle of B's change. Commit C gets blamed
|
||||||
|
# for its change, and that breaks up B's change into multiple blame entries.
|
||||||
|
# When processing B, one of the blame_entries is outside A's window (which was
|
||||||
|
# zero - it had no lines added on its side of the diff).
|
||||||
|
#
|
||||||
|
# A--B--C, ignore B to test the ignore heuristic's boundary checks.
|
||||||
|
test_expect_success ignored_chunk_negative_parent_size '
|
||||||
|
rm -rf .git/ &&
|
||||||
|
git init &&
|
||||||
|
|
||||||
|
test_write_lines L1 L2 L7 L8 L9 >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m A &&
|
||||||
|
git tag A &&
|
||||||
|
|
||||||
|
test_write_lines L1 L2 L3 L4 L5 L6 L7 L8 L9 >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m B &&
|
||||||
|
git tag B &&
|
||||||
|
|
||||||
|
test_write_lines L1 L2 L3 L4 xxx L6 L7 L8 L9 >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m C &&
|
||||||
|
git tag C &&
|
||||||
|
|
||||||
|
git blame file --ignore-rev B >blame_raw
|
||||||
|
'
|
||||||
|
|
||||||
|
# Resetting the repo and creating:
|
||||||
|
#
|
||||||
|
# A--B--M
|
||||||
|
# \ /
|
||||||
|
# C-+
|
||||||
|
#
|
||||||
|
# 'A' creates a file. B changes line 1, and C changes line 9. M merges.
|
||||||
|
test_expect_success ignore_merge '
|
||||||
|
rm -rf .git/ &&
|
||||||
|
git init &&
|
||||||
|
|
||||||
|
test_write_lines L1 L2 L3 L4 L5 L6 L7 L8 L9 >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m A &&
|
||||||
|
git tag A &&
|
||||||
|
|
||||||
|
test_write_lines BB L2 L3 L4 L5 L6 L7 L8 L9 >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m B &&
|
||||||
|
git tag B &&
|
||||||
|
|
||||||
|
git reset --hard A &&
|
||||||
|
test_write_lines L1 L2 L3 L4 L5 L6 L7 L8 CC >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m C &&
|
||||||
|
git tag C &&
|
||||||
|
|
||||||
|
test_merge M B &&
|
||||||
|
git blame --line-porcelain file --ignore-rev M >blame_raw &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
git rev-parse B >expect &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
grep -E "^[0-9a-f]+ [0-9]+ 9" blame_raw | sed -e "s/ .*//" >actual &&
|
||||||
|
git rev-parse C >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
437
t/t8014-blame-ignore-fuzzy.sh
Executable file
437
t/t8014-blame-ignore-fuzzy.sh
Executable file
@ -0,0 +1,437 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='git blame ignore fuzzy heuristic'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
pick_author='s/^[0-9a-f^]* *(\([^ ]*\) .*/\1/'
|
||||||
|
|
||||||
|
# Each test is composed of 4 variables:
|
||||||
|
# titleN - the test name
|
||||||
|
# aN - the initial content
|
||||||
|
# bN - the final content
|
||||||
|
# expectedN - the line numbers from aN that we expect git blame
|
||||||
|
# on bN to identify, or "Final" if bN itself should
|
||||||
|
# be identified as the origin of that line.
|
||||||
|
|
||||||
|
# We start at test 2 because setup will show as test 1
|
||||||
|
title2="Regression test for partially overlapping search ranges"
|
||||||
|
cat <<EOF >a2
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
abcdef
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
ijkl
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
pqrs
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
wxyz
|
||||||
|
17
|
||||||
|
18
|
||||||
|
19
|
||||||
|
EOF
|
||||||
|
cat <<EOF >b2
|
||||||
|
abcde
|
||||||
|
ijk
|
||||||
|
pqr
|
||||||
|
wxy
|
||||||
|
EOF
|
||||||
|
cat <<EOF >expected2
|
||||||
|
4
|
||||||
|
8
|
||||||
|
12
|
||||||
|
16
|
||||||
|
EOF
|
||||||
|
|
||||||
|
title3="Combine 3 lines into 2"
|
||||||
|
cat <<EOF >a3
|
||||||
|
if ((maxgrow==0) ||
|
||||||
|
( single_line_field && (field->dcols < maxgrow)) ||
|
||||||
|
(!single_line_field && (field->drows < maxgrow)))
|
||||||
|
EOF
|
||||||
|
cat <<EOF >b3
|
||||||
|
if ((maxgrow == 0) || (single_line_field && (field->dcols < maxgrow)) ||
|
||||||
|
(!single_line_field && (field->drows < maxgrow))) {
|
||||||
|
EOF
|
||||||
|
cat <<EOF >expected3
|
||||||
|
2
|
||||||
|
3
|
||||||
|
EOF
|
||||||
|
|
||||||
|
title4="Add curly brackets"
|
||||||
|
cat <<EOF >a4
|
||||||
|
if (rows) *rows = field->rows;
|
||||||
|
if (cols) *cols = field->cols;
|
||||||
|
if (frow) *frow = field->frow;
|
||||||
|
if (fcol) *fcol = field->fcol;
|
||||||
|
EOF
|
||||||
|
cat <<EOF >b4
|
||||||
|
if (rows) {
|
||||||
|
*rows = field->rows;
|
||||||
|
}
|
||||||
|
if (cols) {
|
||||||
|
*cols = field->cols;
|
||||||
|
}
|
||||||
|
if (frow) {
|
||||||
|
*frow = field->frow;
|
||||||
|
}
|
||||||
|
if (fcol) {
|
||||||
|
*fcol = field->fcol;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
cat <<EOF >expected4
|
||||||
|
1
|
||||||
|
1
|
||||||
|
Final
|
||||||
|
2
|
||||||
|
2
|
||||||
|
Final
|
||||||
|
3
|
||||||
|
3
|
||||||
|
Final
|
||||||
|
4
|
||||||
|
4
|
||||||
|
Final
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
title5="Combine many lines and change case"
|
||||||
|
cat <<EOF >a5
|
||||||
|
for(row=0,pBuffer=field->buf;
|
||||||
|
row<height;
|
||||||
|
row++,pBuffer+=width )
|
||||||
|
{
|
||||||
|
if ((len = (int)( After_End_Of_Data( pBuffer, width ) - pBuffer )) > 0)
|
||||||
|
{
|
||||||
|
wmove( win, row, 0 );
|
||||||
|
waddnstr( win, pBuffer, len );
|
||||||
|
EOF
|
||||||
|
cat <<EOF >b5
|
||||||
|
for (Row = 0, PBuffer = field->buf; Row < Height; Row++, PBuffer += Width) {
|
||||||
|
if ((Len = (int)(afterEndOfData(PBuffer, Width) - PBuffer)) > 0) {
|
||||||
|
wmove(win, Row, 0);
|
||||||
|
waddnstr(win, PBuffer, Len);
|
||||||
|
EOF
|
||||||
|
cat <<EOF >expected5
|
||||||
|
1
|
||||||
|
5
|
||||||
|
7
|
||||||
|
8
|
||||||
|
EOF
|
||||||
|
|
||||||
|
title6="Rename and combine lines"
|
||||||
|
cat <<EOF >a6
|
||||||
|
bool need_visual_update = ((form != (FORM *)0) &&
|
||||||
|
(form->status & _POSTED) &&
|
||||||
|
(form->current==field));
|
||||||
|
|
||||||
|
if (need_visual_update)
|
||||||
|
Synchronize_Buffer(form);
|
||||||
|
|
||||||
|
if (single_line_field)
|
||||||
|
{
|
||||||
|
growth = field->cols * amount;
|
||||||
|
if (field->maxgrow)
|
||||||
|
growth = Minimum(field->maxgrow - field->dcols,growth);
|
||||||
|
field->dcols += growth;
|
||||||
|
if (field->dcols == field->maxgrow)
|
||||||
|
EOF
|
||||||
|
cat <<EOF >b6
|
||||||
|
bool NeedVisualUpdate = ((Form != (FORM *)0) && (Form->status & _POSTED) &&
|
||||||
|
(Form->current == field));
|
||||||
|
|
||||||
|
if (NeedVisualUpdate) {
|
||||||
|
synchronizeBuffer(Form);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SingleLineField) {
|
||||||
|
Growth = field->cols * amount;
|
||||||
|
if (field->maxgrow) {
|
||||||
|
Growth = Minimum(field->maxgrow - field->dcols, Growth);
|
||||||
|
}
|
||||||
|
field->dcols += Growth;
|
||||||
|
if (field->dcols == field->maxgrow) {
|
||||||
|
EOF
|
||||||
|
cat <<EOF >expected6
|
||||||
|
1
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
Final
|
||||||
|
7
|
||||||
|
8
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
Final
|
||||||
|
13
|
||||||
|
14
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Both lines match identically so position must be used to tie-break.
|
||||||
|
title7="Same line twice"
|
||||||
|
cat <<EOF >a7
|
||||||
|
abc
|
||||||
|
abc
|
||||||
|
EOF
|
||||||
|
cat <<EOF >b7
|
||||||
|
abcd
|
||||||
|
abcd
|
||||||
|
EOF
|
||||||
|
cat <<EOF >expected7
|
||||||
|
1
|
||||||
|
2
|
||||||
|
EOF
|
||||||
|
|
||||||
|
title8="Enforce line order"
|
||||||
|
cat <<EOF >a8
|
||||||
|
abcdef
|
||||||
|
ghijkl
|
||||||
|
ab
|
||||||
|
EOF
|
||||||
|
cat <<EOF >b8
|
||||||
|
ghijk
|
||||||
|
abcd
|
||||||
|
EOF
|
||||||
|
cat <<EOF >expected8
|
||||||
|
2
|
||||||
|
3
|
||||||
|
EOF
|
||||||
|
|
||||||
|
title9="Expand lines and rename variables"
|
||||||
|
cat <<EOF >a9
|
||||||
|
int myFunction(int ArgumentOne, Thing *ArgTwo, Blah XuglyBug) {
|
||||||
|
Squiggle FabulousResult = squargle(ArgumentOne, *ArgTwo,
|
||||||
|
XuglyBug) + EwwwGlobalWithAReallyLongNameYepTooLong;
|
||||||
|
return FabulousResult * 42;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
cat <<EOF >b9
|
||||||
|
int myFunction(int argument_one, Thing *arg_asdfgh,
|
||||||
|
Blah xugly_bug) {
|
||||||
|
Squiggle fabulous_result = squargle(argument_one,
|
||||||
|
*arg_asdfgh, xugly_bug)
|
||||||
|
+ g_ewww_global_with_a_really_long_name_yep_too_long;
|
||||||
|
return fabulous_result * 42;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
cat <<EOF >expected9
|
||||||
|
1
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
EOF
|
||||||
|
|
||||||
|
title10="Two close matches versus one less close match"
|
||||||
|
cat <<EOF >a10
|
||||||
|
abcdef
|
||||||
|
abcdef
|
||||||
|
ghijkl
|
||||||
|
EOF
|
||||||
|
cat <<EOF >b10
|
||||||
|
gh
|
||||||
|
abcdefx
|
||||||
|
EOF
|
||||||
|
cat <<EOF >expected10
|
||||||
|
Final
|
||||||
|
2
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# The first line of b matches best with the last line of a, but the overall
|
||||||
|
# match is better if we match it with the the first line of a.
|
||||||
|
title11="Piggy in the middle"
|
||||||
|
cat <<EOF >a11
|
||||||
|
abcdefg
|
||||||
|
ijklmn
|
||||||
|
abcdefgh
|
||||||
|
EOF
|
||||||
|
cat <<EOF >b11
|
||||||
|
abcdefghx
|
||||||
|
ijklm
|
||||||
|
EOF
|
||||||
|
cat <<EOF >expected11
|
||||||
|
1
|
||||||
|
2
|
||||||
|
EOF
|
||||||
|
|
||||||
|
title12="No trailing newline"
|
||||||
|
printf "abc\ndef" >a12
|
||||||
|
printf "abx\nstu" >b12
|
||||||
|
cat <<EOF >expected12
|
||||||
|
1
|
||||||
|
Final
|
||||||
|
EOF
|
||||||
|
|
||||||
|
title13="Reorder includes"
|
||||||
|
cat <<EOF >a13
|
||||||
|
#include "c.h"
|
||||||
|
#include "b.h"
|
||||||
|
#include "a.h"
|
||||||
|
#include "e.h"
|
||||||
|
#include "d.h"
|
||||||
|
EOF
|
||||||
|
cat <<EOF >b13
|
||||||
|
#include "a.h"
|
||||||
|
#include "b.h"
|
||||||
|
#include "c.h"
|
||||||
|
#include "d.h"
|
||||||
|
#include "e.h"
|
||||||
|
EOF
|
||||||
|
cat <<EOF >expected13
|
||||||
|
3
|
||||||
|
2
|
||||||
|
1
|
||||||
|
5
|
||||||
|
4
|
||||||
|
EOF
|
||||||
|
|
||||||
|
last_test=13
|
||||||
|
|
||||||
|
test_expect_success setup '
|
||||||
|
for i in $(test_seq 2 $last_test)
|
||||||
|
do
|
||||||
|
# Append each line in a separate commit to make it easy to
|
||||||
|
# check which original line the blame output relates to.
|
||||||
|
|
||||||
|
line_count=0 &&
|
||||||
|
while IFS= read line
|
||||||
|
do
|
||||||
|
line_count=$((line_count+1)) &&
|
||||||
|
echo "$line" >>"$i" &&
|
||||||
|
git add "$i" &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME="$line_count" git commit -m "$line_count"
|
||||||
|
done <"a$i"
|
||||||
|
done &&
|
||||||
|
|
||||||
|
for i in $(test_seq 2 $last_test)
|
||||||
|
do
|
||||||
|
# Overwrite the files with the final content.
|
||||||
|
cp b$i $i &&
|
||||||
|
git add $i
|
||||||
|
done &&
|
||||||
|
test_tick &&
|
||||||
|
|
||||||
|
# Commit the final content all at once so it can all be
|
||||||
|
# referred to with the same commit ID.
|
||||||
|
GIT_AUTHOR_NAME=Final git commit -m Final &&
|
||||||
|
|
||||||
|
IGNOREME=$(git rev-parse HEAD)
|
||||||
|
'
|
||||||
|
|
||||||
|
for i in $(test_seq 2 $last_test); do
|
||||||
|
eval title="\$title$i"
|
||||||
|
test_expect_success "$title" \
|
||||||
|
"git blame -M9 --ignore-rev $IGNOREME $i >output &&
|
||||||
|
sed -e \"$pick_author\" output >actual &&
|
||||||
|
test_cmp expected$i actual"
|
||||||
|
done
|
||||||
|
|
||||||
|
# This invoked a null pointer dereference when the chunk callback was called
|
||||||
|
# with a zero length parent chunk and there were no more suspects.
|
||||||
|
test_expect_success 'Diff chunks with no suspects' '
|
||||||
|
test_write_lines xy1 A B C xy1 >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=1 git commit -m 1 &&
|
||||||
|
|
||||||
|
test_write_lines xy2 A B xy2 C xy2 >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=2 git commit -m 2 &&
|
||||||
|
REV_2=$(git rev-parse HEAD) &&
|
||||||
|
|
||||||
|
test_write_lines xy3 A >file &&
|
||||||
|
git add file &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=3 git commit -m 3 &&
|
||||||
|
REV_3=$(git rev-parse HEAD) &&
|
||||||
|
|
||||||
|
test_write_lines 1 1 >expected &&
|
||||||
|
|
||||||
|
git blame --ignore-rev $REV_2 --ignore-rev $REV_3 file >output &&
|
||||||
|
sed -e "$pick_author" output >actual &&
|
||||||
|
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'position matching' '
|
||||||
|
test_write_lines abc def >file2 &&
|
||||||
|
git add file2 &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=1 git commit -m 1 &&
|
||||||
|
|
||||||
|
test_write_lines abc def abc def >file2 &&
|
||||||
|
git add file2 &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=2 git commit -m 2 &&
|
||||||
|
|
||||||
|
test_write_lines abcx defx abcx defx >file2 &&
|
||||||
|
git add file2 &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=3 git commit -m 3 &&
|
||||||
|
REV_3=$(git rev-parse HEAD) &&
|
||||||
|
|
||||||
|
test_write_lines abcy defy abcx defx >file2 &&
|
||||||
|
git add file2 &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=4 git commit -m 4 &&
|
||||||
|
REV_4=$(git rev-parse HEAD) &&
|
||||||
|
|
||||||
|
test_write_lines 1 1 2 2 >expected &&
|
||||||
|
|
||||||
|
git blame --ignore-rev $REV_3 --ignore-rev $REV_4 file2 >output &&
|
||||||
|
sed -e "$pick_author" output >actual &&
|
||||||
|
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# This fails if each blame entry is processed independently instead of
|
||||||
|
# processing each diff change in full.
|
||||||
|
test_expect_success 'preserve order' '
|
||||||
|
test_write_lines bcde >file3 &&
|
||||||
|
git add file3 &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=1 git commit -m 1 &&
|
||||||
|
|
||||||
|
test_write_lines bcde fghij >file3 &&
|
||||||
|
git add file3 &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=2 git commit -m 2 &&
|
||||||
|
|
||||||
|
test_write_lines bcde fghij abcd >file3 &&
|
||||||
|
git add file3 &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=3 git commit -m 3 &&
|
||||||
|
|
||||||
|
test_write_lines abcdx fghijx bcdex >file3 &&
|
||||||
|
git add file3 &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=4 git commit -m 4 &&
|
||||||
|
REV_4=$(git rev-parse HEAD) &&
|
||||||
|
|
||||||
|
test_write_lines abcdx fghijy bcdex >file3 &&
|
||||||
|
git add file3 &&
|
||||||
|
test_tick &&
|
||||||
|
GIT_AUTHOR_NAME=5 git commit -m 5 &&
|
||||||
|
REV_5=$(git rev-parse HEAD) &&
|
||||||
|
|
||||||
|
test_write_lines 1 2 3 >expected &&
|
||||||
|
|
||||||
|
git blame --ignore-rev $REV_4 --ignore-rev $REV_5 file3 >output &&
|
||||||
|
sed -e "$pick_author" output >actual &&
|
||||||
|
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Loading…
Reference in New Issue
Block a user