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 <start>,<end>::
-L :<regex>:: -L :<regex>::
Annotate only the given line range. <start> and <end> are optional. Annotate only the given line range. May be specified multiple times.
``-L <start>'' or ``-L <start>,'' spans from <start> to end of file. Overlapping ranges are allowed.
``-L ,<end>'' spans from start of file to <end>. +
<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[] include::line-range-format.txt[]
-l:: -l::

View File

@ -9,7 +9,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 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> [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file>
DESCRIPTION DESCRIPTION
@ -18,7 +18,8 @@ DESCRIPTION
Annotates each line in the given file with information from the revision which Annotates each line in the given file with information from the revision which
last modified the line. Optionally, start annotating from the given revision. 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 The origin of lines is automatically followed across whole-file
renames (currently there is no option to turn the rename-following 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 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 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 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 (they mean the same thing -- both ask for 21 lines starting at
line 40): line 40):

View File

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

View File

@ -1,3 +1,5 @@
<start> and <end> can take one of these forms:
- number - number
+ +
If <start> or <end> is a number, it specifies an If <start> or <end> is a number, it specifies an
@ -7,7 +9,10 @@ absolute line number (lines count from 1).
- /regex/ - /regex/
+ +
This form will use the first line matching the given 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>. 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 This is only valid for <end> and will specify a number
of lines before or after the line given by <start>. 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 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 "utf8.h"
#include "userdiff.h" #include "userdiff.h"
#include "line-range.h" #include "line-range.h"
#include "line-log.h"
static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file"); 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); 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) static int git_blame_config(const char *var, const char *value, void *cb)
{ {
if (!strcmp(var, "blame.showroot")) { if (!strcmp(var, "blame.showroot")) {
@ -2245,29 +2234,18 @@ static int blame_move_callback(const struct option *option, const char *arg, int
return 0; 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) int cmd_blame(int argc, const char **argv, const char *prefix)
{ {
struct rev_info revs; struct rev_info revs;
const char *path; const char *path;
struct scoreboard sb; struct scoreboard sb;
struct origin *o; struct origin *o;
struct blame_entry *ent; struct blame_entry *ent = NULL;
long dashdash_pos, bottom, top, lno; long dashdash_pos, lno;
const char *final_commit_name = NULL; const char *final_commit_name = NULL;
enum object_type type; enum object_type type;
static const char *bottomtop = NULL; static struct string_list range_list;
static int output_option = 0, opt = 0; static int output_option = 0, opt = 0;
static int show_stats = 0; static int show_stats = 0;
static const char *revs_file = NULL; 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")), 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, '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 }, { 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__ABBREV(&abbrev),
OPT_END() OPT_END()
}; };
struct parse_opt_ctx_t ctx; struct parse_opt_ctx_t ctx;
int cmd_is_annotate = !strcmp(argv[0], "annotate"); int cmd_is_annotate = !strcmp(argv[0], "annotate");
struct range_set ranges;
unsigned int range_i;
long anchor;
git_config(git_blame_config, NULL); git_config(git_blame_config, NULL);
init_revisions(&revs, NULL); init_revisions(&revs, NULL);
@ -2492,22 +2473,48 @@ parse_done:
num_read_blob++; num_read_blob++;
lno = prepare_lines(&sb); lno = prepare_lines(&sb);
bottom = top = 0; if (lno && !range_list.nr)
if (bottomtop) string_list_append(&range_list, xstrdup("1"));
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--;
ent = xcalloc(1, sizeof(*ent)); anchor = 1;
ent->lno = bottom; range_set_init(&ranges, range_list.nr);
ent->num_lines = top - bottom; for (range_i = 0; range_i < range_list.nr; ++range_i) {
ent->suspect = o; long bottom, top;
ent->s_lno = bottom; 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.ent = ent;
sb.path = path; 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 */ /* Either initialization would be fine */
#define RANGE_SET_INIT {0} #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->alloc = rs->nr = 0;
rs->ranges = NULL; rs->ranges = NULL;
@ -31,7 +31,7 @@ static void range_set_init(struct range_set *rs, size_t prealloc)
range_set_grow(rs, 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); free(rs->ranges);
rs->alloc = rs->nr = 0; 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_ */ /* 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); assert(a <= b);
range_set_grow(rs, 1); 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++; 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); assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);
range_set_append_unsafe(rs, a, b); 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, * 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 * 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 i;
int o = 0; /* output cursor */ int o = 0; /* output cursor */
@ -291,7 +291,6 @@ static void line_log_data_insert(struct line_log_data **list,
if (p) { if (p) {
range_set_append_unsafe(&p->ranges, begin, end); range_set_append_unsafe(&p->ranges, begin, end);
sort_and_merge_range_set(&p->ranges);
free(path); free(path);
return; 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 = xcalloc(1, sizeof(struct line_log_data));
p->path = path; p->path = path;
range_set_append(&p->ranges, begin, end); range_set_append(&p->ranges, begin, end);
sort_and_merge_range_set(&p->ranges);
if (ip) { if (ip) {
p->next = ip->next; p->next = ip->next;
ip->next = p; 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 nth_line_cb cb_data;
struct string_list_item *item; struct string_list_item *item;
struct line_log_data *ranges = NULL; struct line_log_data *ranges = NULL;
struct line_log_data *p;
for_each_string_list_item(item, args) { for_each_string_list_item(item, args) {
const char *name_part, *range_part; const char *name_part, *range_part;
char *full_name; char *full_name;
struct diff_filespec *spec; struct diff_filespec *spec;
long begin = 0, end = 0; long begin = 0, end = 0;
long anchor;
name_part = skip_range_arg(item->string); name_part = skip_range_arg(item->string);
if (!name_part || *name_part != ':' || !name_part[1]) 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.lines = lines;
cb_data.line_ends = ends; 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, if (parse_range_arg(range_part, nth_line, &cb_data,
lines, &begin, &end, lines, anchor, &begin, &end,
full_name)) full_name))
die("malformed -L argument '%s'", range_part); die("malformed -L argument '%s'", range_part);
if (lines < end || ((lines || begin) && lines < begin)) 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; ends = NULL;
} }
for (p = ranges; p; p = p->next)
sort_and_merge_range_set(&p->ranges);
return ranges; return ranges;
} }

View File

@ -25,6 +25,18 @@ struct diff_ranges {
struct range_set target; 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 /* Linked list of interesting files and their associated ranges. The
* list must be kept sorted by path. * list must be kept sorted by path.
* *

View File

@ -6,6 +6,18 @@
/* /*
* Parse one item in the -L option * 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, static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
void *data, long lines, long begin, long *ret) 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); num = strtol(spec, &term, 10);
if (term != spec) { if (term != spec) {
if (ret) if (ret) {
if (num <= 0)
die("-L invalid line number: %ld", num);
*ret = num; *ret = num;
}
return term; return term;
} }
if (begin < 0) {
if (spec[0] != '^')
begin = -begin;
else {
begin = 1;
spec++;
}
}
if (spec[0] != '/') if (spec[0] != '/')
return spec; return spec;
@ -85,7 +110,8 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
else { else {
char errbuf[1024]; char errbuf[1024];
regerror(reg_error, &regexp, 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, 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) const char *path)
{ {
char *pattern; char *pattern;
@ -150,6 +176,11 @@ static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_
int reg_error; int reg_error;
regex_t regexp; regex_t regexp;
if (*arg == '^') {
anchor = 1;
arg++;
}
assert(*arg == ':'); assert(*arg == ':');
term = arg+1; term = arg+1;
while (*term && *term != ':') { 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)); 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); drv = userdiff_find_by_path(path);
if (drv && drv->funcname.pattern) { 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); p = find_funcname_matching_regexp(xecfg, (char*) start, &regexp);
if (!p) if (!p)
die("-L parameter '%s': no match", pattern); die("-L parameter '%s' starting at line %ld: no match",
pattern, anchor + 1);
*begin = 0; *begin = 0;
while (p > nth_line_cb(cb_data, *begin)) while (p > nth_line_cb(cb_data, *begin))
(*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, int parse_range_arg(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,
const char *path) long *begin, long *end, const char *path)
{ {
*begin = *end = 0; *begin = *end = 0;
if (*arg == ':') { if (anchor < 1)
arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path); 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) if (!arg || *arg)
return -1; return -1;
return 0; 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 == ',') if (*arg == ',')
arg = parse_loc(arg + 1, nth_line_cb, cb_data, lines, *begin + 1, end); 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) const char *skip_range_arg(const char *arg)
{ {
if (*arg == ':') if (*arg == ':' || (*arg == '^' && *(arg + 1) == ':'))
return parse_range_funcname(arg, NULL, NULL, 0, NULL, NULL, NULL); return parse_range_funcname(arg, NULL, NULL, 0, 0, NULL, NULL, NULL);
arg = parse_loc(arg, NULL, NULL, 0, -1, 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 * 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. * 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 * 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 * actual range is stored in *begin and *end. The counting starts
* at 1! In case of error, the caller should show usage message. * 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, extern int parse_range_arg(const char *arg,
nth_line_fn_t nth_line_cb, nth_line_fn_t nth_line_cb,
void *cb_data, long lines, void *cb_data, long lines, long anchor,
long *begin, long *end, long *begin, long *end,
const char *path); 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 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_expect_success 'blame -L ,+0' '
test_must_fail $PROG -L,+0 file 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_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' ' test_expect_success 'setup -L :regex' '
tr Q "\\t" >hello.c <<-\EOF && tr Q "\\t" >hello.c <<-\EOF &&
int main(int argc, const char *argv[]) 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_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' ' test_expect_success 'setup incremental' '
( (
GIT_AUTHOR_NAME=I && GIT_AUTHOR_NAME=I &&
@ -333,8 +447,8 @@ test_expect_success 'blame empty' '
check_count -h HEAD^^ -f incremental check_count -h HEAD^^ -f incremental
' '
test_expect_success 'blame -L 0 empty (undocumented)' ' test_expect_success 'blame -L 0 empty' '
check_count -h HEAD^^ -f incremental -L0 test_must_fail $PROG -L0 incremental HEAD^^
' '
test_expect_success 'blame -L 1 empty' ' test_expect_success 'blame -L 1 empty' '
@ -349,8 +463,8 @@ test_expect_success 'blame half' '
check_count -h HEAD^ -f incremental I 1 check_count -h HEAD^ -f incremental I 1
' '
test_expect_success 'blame -L 0 half (undocumented)' ' test_expect_success 'blame -L 0 half' '
check_count -h HEAD^ -f incremental -L0 I 1 test_must_fail $PROG -L0 incremental HEAD^
' '
test_expect_success 'blame -L 1 half' ' test_expect_success 'blame -L 1 half' '
@ -369,8 +483,8 @@ test_expect_success 'blame full' '
check_count -f incremental I 1 check_count -f incremental I 1
' '
test_expect_success 'blame -L 0 full (undocumented)' ' test_expect_success 'blame -L 0 full' '
check_count -f incremental -L0 I 1 test_must_fail $PROG -L0 incremental
' '
test_expect_success 'blame -L 1 full' ' 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_expect_success 'blame -L X,-N (non-numeric N)' '
test_must_fail $PROG -L1,-N file 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 "-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,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 :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 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 canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset