Merge branch 'es/blame-L-twice'

Teaches "git blame" to take more than one -L ranges.

* es/blame-L-twice:
  line-range: reject -L line numbers less than 1
  t8001/t8002: blame: add tests of -L line numbers less than 1
  line-range: teach -L^:RE to search from start of file
  line-range: teach -L:RE to search from end of previous -L range
  line-range: teach -L^/RE/ to search from start of file
  line-range-format.txt: document -L/RE/ relative search
  log: teach -L/RE/ to search from end of previous -L range
  blame: teach -L/RE/ to search from end of previous -L range
  line-range: teach -L/RE/ to search relative to anchor point
  blame: document multiple -L support
  t8001/t8002: blame: add tests of multiple -L options
  blame: accept multiple -L ranges
  blame: inline one-line function into its lone caller
  range-set: publish API for re-use by git-blame -L
  line-range-format.txt: clarify -L:regex usage form
  git-log.txt: place each -L option variation on its own line
This commit is contained in:
Junio C Hamano 2013-09-09 14:35:11 -07:00
commit de9a25354a
11 changed files with 281 additions and 87 deletions

View File

@ -11,12 +11,12 @@
-L <start>,<end>::
-L :<regex>::
Annotate only the given line range. <start> and <end> are optional.
``-L <start>'' or ``-L <start>,'' spans from <start> to end of file.
``-L ,<end>'' spans from start of file to <end>.
Annotate only the given line range. May be specified multiple times.
Overlapping ranges are allowed.
+
<start> and <end> are optional. ``-L <start>'' or ``-L <start>,'' spans from
<start> to end of file. ``-L ,<end>'' spans from start of file to <end>.
+
<start> and <end> can take one of these forms:
include::line-range-format.txt[]
-l::

View File

@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
[-L n,m | -L :fn] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
[-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
[--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file>
DESCRIPTION
@ -18,7 +18,8 @@ DESCRIPTION
Annotates each line in the given file with information from the revision which
last modified the line. Optionally, start annotating from the given revision.
The command can also limit the range of lines annotated.
When specified one or more times, `-L` restricts annotation to the requested
lines.
The origin of lines is automatically followed across whole-file
renames (currently there is no option to turn the rename-following
@ -130,7 +131,10 @@ SPECIFYING RANGES
Unlike 'git blame' and 'git annotate' in older versions of git, the extent
of the annotation can be limited to both line ranges and revision
ranges. When you are interested in finding the origin for
ranges. The `-L` option, which limits annotation to a range of lines, may be
specified multiple times.
When you are interested in finding the origin for
lines 40-60 for file `foo`, you can use the `-L` option like so
(they mean the same thing -- both ask for 21 lines starting at
line 40):

View File

@ -62,7 +62,8 @@ produced by --stat etc.
Note that only message is considered, if also a diff is shown
its size is not included.
-L <start>,<end>:<file>, -L :<regex>:<file>::
-L <start>,<end>:<file>::
-L :<regex>:<file>::
Trace the evolution of the line range given by "<start>,<end>"
(or the funcname regex <regex>) within the <file>. You may
@ -71,8 +72,6 @@ produced by --stat etc.
give zero or one positive revision arguments.
You can specify this option more than once.
+
<start> and <end> can take one of these forms:
include::line-range-format.txt[]
<revision range>::

View File

@ -1,3 +1,5 @@
<start> and <end> can take one of these forms:
- number
+
If <start> or <end> is a number, it specifies an
@ -7,7 +9,10 @@ absolute line number (lines count from 1).
- /regex/
+
This form will use the first line matching the given
POSIX regex. If <end> is a regex, it will search
POSIX regex. If <start> is a regex, it will search from the end of
the previous `-L` range, if any, otherwise from the start of file.
If <start> is ``^/regex/'', it will search from the start of file.
If <end> is a regex, it will search
starting at the line given by <start>.
+
@ -15,11 +20,10 @@ starting at the line given by <start>.
+
This is only valid for <end> and will specify a number
of lines before or after the line given by <start>.
+
- :regex
+
If the option's argument is of the form :regex, it denotes the range
If ``:<regex>'' is given in place of <start> and <end>, it denotes the range
from the first funcname line that matches <regex>, up to the next
funcname line.
+
funcname line. ``:<regex>'' searches from the end of the previous `-L` range,
if any, otherwise from the start of file.
``^:<regex>'' searches from the start of file.

View File

@ -22,6 +22,7 @@
#include "utf8.h"
#include "userdiff.h"
#include "line-range.h"
#include "line-log.h"
static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
@ -1937,18 +1938,6 @@ static const char *add_prefix(const char *prefix, const char *path)
return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
}
/*
* Parsing of -L option
*/
static void prepare_blame_range(struct scoreboard *sb,
const char *bottomtop,
long lno,
long *bottom, long *top)
{
if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))
usage(blame_usage);
}
static int git_blame_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "blame.showroot")) {
@ -2245,29 +2234,18 @@ static int blame_move_callback(const struct option *option, const char *arg, int
return 0;
}
static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
{
const char **bottomtop = option->value;
if (!arg)
return -1;
if (*bottomtop)
die("More than one '-L n,m' option given");
*bottomtop = arg;
return 0;
}
int cmd_blame(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
const char *path;
struct scoreboard sb;
struct origin *o;
struct blame_entry *ent;
long dashdash_pos, bottom, top, lno;
struct blame_entry *ent = NULL;
long dashdash_pos, lno;
const char *final_commit_name = NULL;
enum object_type type;
static const char *bottomtop = NULL;
static struct string_list range_list;
static int output_option = 0, opt = 0;
static int show_stats = 0;
static const char *revs_file = NULL;
@ -2293,13 +2271,16 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
{ OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback },
{ OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback },
OPT_CALLBACK('L', NULL, &bottomtop, N_("n,m"), N_("Process only line range n,m, counting from 1"), blame_bottomtop_callback),
OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")),
OPT__ABBREV(&abbrev),
OPT_END()
};
struct parse_opt_ctx_t ctx;
int cmd_is_annotate = !strcmp(argv[0], "annotate");
struct range_set ranges;
unsigned int range_i;
long anchor;
git_config(git_blame_config, NULL);
init_revisions(&revs, NULL);
@ -2492,22 +2473,48 @@ parse_done:
num_read_blob++;
lno = prepare_lines(&sb);
bottom = top = 0;
if (bottomtop)
prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
if (lno < top || ((lno || bottom) && lno < bottom))
die("file %s has only %lu lines", path, lno);
if (bottom < 1)
bottom = 1;
if (top < 1)
top = lno;
bottom--;
if (lno && !range_list.nr)
string_list_append(&range_list, xstrdup("1"));
ent = xcalloc(1, sizeof(*ent));
ent->lno = bottom;
ent->num_lines = top - bottom;
ent->suspect = o;
ent->s_lno = bottom;
anchor = 1;
range_set_init(&ranges, range_list.nr);
for (range_i = 0; range_i < range_list.nr; ++range_i) {
long bottom, top;
if (parse_range_arg(range_list.items[range_i].string,
nth_line_cb, &sb, lno, anchor,
&bottom, &top, sb.path))
usage(blame_usage);
if (lno < top || ((lno || bottom) && lno < bottom))
die("file %s has only %lu lines", path, lno);
if (bottom < 1)
bottom = 1;
if (top < 1)
top = lno;
bottom--;
range_set_append_unsafe(&ranges, bottom, top);
anchor = top + 1;
}
sort_and_merge_range_set(&ranges);
for (range_i = ranges.nr; range_i > 0; --range_i) {
const struct range *r = &ranges.ranges[range_i - 1];
long bottom = r->start;
long top = r->end;
struct blame_entry *next = ent;
ent = xcalloc(1, sizeof(*ent));
ent->lno = bottom;
ent->num_lines = top - bottom;
ent->suspect = o;
ent->s_lno = bottom;
ent->next = next;
if (next)
next->prev = ent;
origin_incref(o);
}
origin_decref(o);
range_set_release(&ranges);
string_list_clear(&range_list, 0);
sb.ent = ent;
sb.path = path;

View File

@ -23,7 +23,7 @@ static void range_set_grow(struct range_set *rs, size_t extra)
/* Either initialization would be fine */
#define RANGE_SET_INIT {0}
static void range_set_init(struct range_set *rs, size_t prealloc)
void range_set_init(struct range_set *rs, size_t prealloc)
{
rs->alloc = rs->nr = 0;
rs->ranges = NULL;
@ -31,7 +31,7 @@ static void range_set_init(struct range_set *rs, size_t prealloc)
range_set_grow(rs, prealloc);
}
static void range_set_release(struct range_set *rs)
void range_set_release(struct range_set *rs)
{
free(rs->ranges);
rs->alloc = rs->nr = 0;
@ -56,7 +56,7 @@ static void range_set_move(struct range_set *dst, struct range_set *src)
}
/* tack on a _new_ range _at the end_ */
static void range_set_append_unsafe(struct range_set *rs, long a, long b)
void range_set_append_unsafe(struct range_set *rs, long a, long b)
{
assert(a <= b);
range_set_grow(rs, 1);
@ -65,7 +65,7 @@ static void range_set_append_unsafe(struct range_set *rs, long a, long b)
rs->nr++;
}
static void range_set_append(struct range_set *rs, long a, long b)
void range_set_append(struct range_set *rs, long a, long b)
{
assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);
range_set_append_unsafe(rs, a, b);
@ -107,7 +107,7 @@ static void range_set_check_invariants(struct range_set *rs)
* In-place pass of sorting and merging the ranges in the range set,
* to establish the invariants when we get the ranges from the user
*/
static void sort_and_merge_range_set(struct range_set *rs)
void sort_and_merge_range_set(struct range_set *rs)
{
int i;
int o = 0; /* output cursor */
@ -291,7 +291,6 @@ static void line_log_data_insert(struct line_log_data **list,
if (p) {
range_set_append_unsafe(&p->ranges, begin, end);
sort_and_merge_range_set(&p->ranges);
free(path);
return;
}
@ -299,7 +298,6 @@ static void line_log_data_insert(struct line_log_data **list,
p = xcalloc(1, sizeof(struct line_log_data));
p->path = path;
range_set_append(&p->ranges, begin, end);
sort_and_merge_range_set(&p->ranges);
if (ip) {
p->next = ip->next;
ip->next = p;
@ -566,12 +564,14 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
struct nth_line_cb cb_data;
struct string_list_item *item;
struct line_log_data *ranges = NULL;
struct line_log_data *p;
for_each_string_list_item(item, args) {
const char *name_part, *range_part;
char *full_name;
struct diff_filespec *spec;
long begin = 0, end = 0;
long anchor;
name_part = skip_range_arg(item->string);
if (!name_part || *name_part != ':' || !name_part[1])
@ -590,8 +590,14 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
cb_data.lines = lines;
cb_data.line_ends = ends;
p = search_line_log_data(ranges, full_name, NULL);
if (p && p->ranges.nr)
anchor = p->ranges.ranges[p->ranges.nr - 1].end + 1;
else
anchor = 1;
if (parse_range_arg(range_part, nth_line, &cb_data,
lines, &begin, &end,
lines, anchor, &begin, &end,
full_name))
die("malformed -L argument '%s'", range_part);
if (lines < end || ((lines || begin) && lines < begin))
@ -608,6 +614,9 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
ends = NULL;
}
for (p = ranges; p; p = p->next)
sort_and_merge_range_set(&p->ranges);
return ranges;
}

View File

@ -25,6 +25,18 @@ struct diff_ranges {
struct range_set target;
};
extern void range_set_init(struct range_set *, size_t prealloc);
extern void range_set_release(struct range_set *);
/* Range includes start; excludes end */
extern void range_set_append_unsafe(struct range_set *, long start, long end);
/* New range must begin at or after end of last added range */
extern void range_set_append(struct range_set *, long start, long end);
/*
* In-place pass of sorting and merging the ranges in the range set,
* to sort and make the ranges disjoint.
*/
extern void sort_and_merge_range_set(struct range_set *);
/* Linked list of interesting files and their associated ranges. The
* list must be kept sorted by path.
*

View File

@ -6,6 +6,18 @@
/*
* Parse one item in the -L option
*
* 'begin' is applicable only to relative range anchors. Absolute anchors
* ignore this value.
*
* When parsing "-L A,B", parse_loc() is called once for A and once for B.
*
* When parsing A, 'begin' must be a negative number, the absolute value of
* which is the line at which relative start-of-range anchors should be
* based. Beginning of file is represented by -1.
*
* When parsing B, 'begin' must be the positive line number immediately
* following the line computed for 'A'.
*/
static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
void *data, long lines, long begin, long *ret)
@ -42,10 +54,23 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
}
num = strtol(spec, &term, 10);
if (term != spec) {
if (ret)
if (ret) {
if (num <= 0)
die("-L invalid line number: %ld", num);
*ret = num;
}
return term;
}
if (begin < 0) {
if (spec[0] != '^')
begin = -begin;
else {
begin = 1;
spec++;
}
}
if (spec[0] != '/')
return spec;
@ -85,7 +110,8 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
else {
char errbuf[1024];
regerror(reg_error, &regexp, errbuf, 1024);
die("-L parameter '%s': %s", spec + 1, errbuf);
die("-L parameter '%s' starting at line %ld: %s",
spec + 1, begin + 1, errbuf);
}
}
@ -138,7 +164,7 @@ static const char *find_funcname_matching_regexp(xdemitconf_t *xecfg, const char
}
static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_cb,
void *cb_data, long lines, long *begin, long *end,
void *cb_data, long lines, long anchor, long *begin, long *end,
const char *path)
{
char *pattern;
@ -150,6 +176,11 @@ static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_
int reg_error;
regex_t regexp;
if (*arg == '^') {
anchor = 1;
arg++;
}
assert(*arg == ':');
term = arg+1;
while (*term && *term != ':') {
@ -164,7 +195,8 @@ static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_
pattern = xstrndup(arg+1, term-(arg+1));
start = nth_line_cb(cb_data, 0);
anchor--; /* input is in human terms */
start = nth_line_cb(cb_data, anchor);
drv = userdiff_find_by_path(path);
if (drv && drv->funcname.pattern) {
@ -182,7 +214,8 @@ static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_
p = find_funcname_matching_regexp(xecfg, (char*) start, &regexp);
if (!p)
die("-L parameter '%s': no match", pattern);
die("-L parameter '%s' starting at line %ld: no match",
pattern, anchor + 1);
*begin = 0;
while (p > nth_line_cb(cb_data, *begin))
(*begin)++;
@ -210,19 +243,24 @@ static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_
}
int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
void *cb_data, long lines, long *begin, long *end,
const char *path)
void *cb_data, long lines, long anchor,
long *begin, long *end, const char *path)
{
*begin = *end = 0;
if (*arg == ':') {
arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path);
if (anchor < 1)
anchor = 1;
if (anchor > lines)
anchor = lines + 1;
if (*arg == ':' || (*arg == '^' && *(arg + 1) == ':')) {
arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, anchor, begin, end, path);
if (!arg || *arg)
return -1;
return 0;
}
arg = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin);
arg = parse_loc(arg, nth_line_cb, cb_data, lines, -anchor, begin);
if (*arg == ',')
arg = parse_loc(arg + 1, nth_line_cb, cb_data, lines, *begin + 1, end);
@ -240,8 +278,8 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
const char *skip_range_arg(const char *arg)
{
if (*arg == ':')
return parse_range_funcname(arg, NULL, NULL, 0, NULL, NULL, NULL);
if (*arg == ':' || (*arg == '^' && *(arg + 1) == ':'))
return parse_range_funcname(arg, NULL, NULL, 0, 0, NULL, NULL, NULL);
arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);

View File

@ -9,6 +9,9 @@
* line 'lno' inside the 'cb_data'. The caller is expected to already
* have a suitable map at hand to make this a constant-time lookup.
*
* 'anchor' is the 1-based line at which relative range specifications
* should be anchored. Absolute ranges are unaffected by this value.
*
* Returns 0 in case of success and -1 if there was an error. The
* actual range is stored in *begin and *end. The counting starts
* at 1! In case of error, the caller should show usage message.
@ -18,7 +21,7 @@ typedef const char *(*nth_line_fn_t)(void *data, long lno);
extern int parse_range_arg(const char *arg,
nth_line_fn_t nth_line_cb,
void *cb_data, long lines,
void *cb_data, long lines, long anchor,
long *begin, long *end,
const char *path);

View File

@ -185,6 +185,18 @@ test_expect_success 'blame -L Y,X (undocumented)' '
check_count -L6,3 B 1 B1 1 B2 1 D 1
'
test_expect_success 'blame -L -X' '
test_must_fail $PROG -L-1 file
'
test_expect_success 'blame -L 0' '
test_must_fail $PROG -L0 file
'
test_expect_success 'blame -L ,0' '
test_must_fail $PROG -L,0 file
'
test_expect_success 'blame -L ,+0' '
test_must_fail $PROG -L,+0 file
'
@ -271,6 +283,75 @@ test_expect_success 'blame -L ,Y (Y > nlines)' '
test_must_fail $PROG -L,12345 file
'
test_expect_success 'blame -L multiple (disjoint)' '
check_count -L2,3 -L6,7 A 1 B1 1 B2 1 "A U Thor" 1
'
test_expect_success 'blame -L multiple (disjoint: unordered)' '
check_count -L6,7 -L2,3 A 1 B1 1 B2 1 "A U Thor" 1
'
test_expect_success 'blame -L multiple (adjacent)' '
check_count -L2,3 -L4,5 A 1 B 1 B2 1 D 1
'
test_expect_success 'blame -L multiple (adjacent: unordered)' '
check_count -L4,5 -L2,3 A 1 B 1 B2 1 D 1
'
test_expect_success 'blame -L multiple (overlapping)' '
check_count -L2,4 -L3,5 A 1 B 1 B2 1 D 1
'
test_expect_success 'blame -L multiple (overlapping: unordered)' '
check_count -L3,5 -L2,4 A 1 B 1 B2 1 D 1
'
test_expect_success 'blame -L multiple (superset/subset)' '
check_count -L2,8 -L3,5 A 1 B 1 B1 1 B2 1 C 1 D 1 "A U Thor" 1
'
test_expect_success 'blame -L multiple (superset/subset: unordered)' '
check_count -L3,5 -L2,8 A 1 B 1 B1 1 B2 1 C 1 D 1 "A U Thor" 1
'
test_expect_success 'blame -L /RE/ (relative)' '
check_count -L3,3 -L/fox/ B1 1 B2 1 C 1 D 1 "A U Thor" 1
'
test_expect_success 'blame -L /RE/ (relative: no preceding range)' '
check_count -L/dog/ A 1 B 1 B1 1 B2 1 C 1 D 1 "A U Thor" 1
'
test_expect_success 'blame -L /RE/ (relative: adjacent)' '
check_count -L1,1 -L/dog/,+1 A 1 E 1
'
test_expect_success 'blame -L /RE/ (relative: not found)' '
test_must_fail $PROG -L4,4 -L/dog/ file
'
test_expect_success 'blame -L /RE/ (relative: end-of-file)' '
test_must_fail $PROG -L, -L/$/ file
'
test_expect_success 'blame -L ^/RE/ (absolute)' '
check_count -L3,3 -L^/dog/,+2 A 1 B2 1
'
test_expect_success 'blame -L ^/RE/ (absolute: no preceding range)' '
check_count -L^/dog/,+2 A 1 B2 1
'
test_expect_success 'blame -L ^/RE/ (absolute: not found)' '
test_must_fail $PROG -L4,4 -L^/tambourine/ file
'
test_expect_success 'blame -L ^/RE/ (absolute: end-of-file)' '
n=$(expr $(wc -l <file) + 1) &&
check_count -L$n -L^/$/,+2 A 1 C 1 E 1
'
test_expect_success 'setup -L :regex' '
tr Q "\\t" >hello.c <<-\EOF &&
int main(int argc, const char *argv[])
@ -313,6 +394,39 @@ test_expect_success 'blame -L :nomatch' '
test_must_fail $PROG -L:nomatch hello.c
'
test_expect_success 'blame -L :RE (relative)' '
check_count -f hello.c -L3,3 -L:ma.. F 1 H 4
'
test_expect_success 'blame -L :RE (relative: no preceding range)' '
check_count -f hello.c -L:ma.. F 4 G 1
'
test_expect_success 'blame -L :RE (relative: not found)' '
test_must_fail $PROG -L3,3 -L:tambourine hello.c
'
test_expect_success 'blame -L :RE (relative: end-of-file)' '
test_must_fail $PROG -L, -L:main hello.c
'
test_expect_success 'blame -L ^:RE (absolute)' '
check_count -f hello.c -L3,3 -L^:ma.. F 4 G 1
'
test_expect_success 'blame -L ^:RE (absolute: no preceding range)' '
check_count -f hello.c -L^:ma.. F 4 G 1
'
test_expect_success 'blame -L ^:RE (absolute: not found)' '
test_must_fail $PROG -L4,4 -L^:tambourine hello.c
'
test_expect_success 'blame -L ^:RE (absolute: end-of-file)' '
n=$(printf "%d" $(wc -l <hello.c)) &&
check_count -f hello.c -L$n -L^:ma.. F 4 G 1 H 1
'
test_expect_success 'setup incremental' '
(
GIT_AUTHOR_NAME=I &&
@ -333,8 +447,8 @@ test_expect_success 'blame empty' '
check_count -h HEAD^^ -f incremental
'
test_expect_success 'blame -L 0 empty (undocumented)' '
check_count -h HEAD^^ -f incremental -L0
test_expect_success 'blame -L 0 empty' '
test_must_fail $PROG -L0 incremental HEAD^^
'
test_expect_success 'blame -L 1 empty' '
@ -349,8 +463,8 @@ test_expect_success 'blame half' '
check_count -h HEAD^ -f incremental I 1
'
test_expect_success 'blame -L 0 half (undocumented)' '
check_count -h HEAD^ -f incremental -L0 I 1
test_expect_success 'blame -L 0 half' '
test_must_fail $PROG -L0 incremental HEAD^
'
test_expect_success 'blame -L 1 half' '
@ -369,8 +483,8 @@ test_expect_success 'blame full' '
check_count -f incremental I 1
'
test_expect_success 'blame -L 0 full (undocumented)' '
check_count -f incremental -L0 I 1
test_expect_success 'blame -L 0 full' '
test_must_fail $PROG -L0 incremental
'
test_expect_success 'blame -L 1 full' '
@ -412,3 +526,7 @@ test_expect_success 'blame -L X,+N (non-numeric N)' '
test_expect_success 'blame -L X,-N (non-numeric N)' '
test_must_fail $PROG -L1,-N file
'
test_expect_success 'blame -L ,^/RE/' '
test_must_fail $PROG -L1,^/99/ file
'

View File

@ -48,7 +48,7 @@ canned_test "-M -L '/long f/,/^}/:b.c' move-support" move-support-f
canned_test "-M -L ':f:b.c' parallel-change" parallel-change-f-to-main
canned_test "-L 4,12:a.c -L :main:a.c simple" multiple
canned_test "-L 4,18:a.c -L :main:a.c simple" multiple-overlapping
canned_test "-L 4,18:a.c -L ^:main:a.c simple" multiple-overlapping
canned_test "-L :main:a.c -L 4,18:a.c simple" multiple-overlapping
canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset
canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset