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:
commit
de9a25354a
@ -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::
|
||||
|
@ -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):
|
||||
|
@ -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>::
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
25
line-log.c
25
line-log.c
@ -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;
|
||||
}
|
||||
|
||||
|
12
line-log.h
12
line-log.h
@ -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.
|
||||
*
|
||||
|
62
line-range.c
62
line-range.c
@ -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, ®exp, 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, ®exp);
|
||||
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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
'
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user