Skip to content

Commit de9a253

Browse files
committed
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
2 parents 4ab4a6d + 5ce922a commit de9a253

File tree

11 files changed

+282
-88
lines changed

11 files changed

+282
-88
lines changed

Documentation/blame-options.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111

1212
-L <start>,<end>::
1313
-L :<regex>::
14-
Annotate only the given line range. <start> and <end> are optional.
15-
``-L <start>'' or ``-L <start>,'' spans from <start> to end of file.
16-
``-L ,<end>'' spans from start of file to <end>.
14+
Annotate only the given line range. May be specified multiple times.
15+
Overlapping ranges are allowed.
16+
+
17+
<start> and <end> are optional. ``-L <start>'' or ``-L <start>,'' spans from
18+
<start> to end of file. ``-L ,<end>'' spans from start of file to <end>.
1719
+
18-
<start> and <end> can take one of these forms:
19-
2020
include::line-range-format.txt[]
2121

2222
-l::

Documentation/git-blame.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ SYNOPSIS
99
--------
1010
[verse]
1111
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
12-
[-L n,m | -L :fn] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
12+
[-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
1313
[--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file>
1414

1515
DESCRIPTION
@@ -18,7 +18,8 @@ DESCRIPTION
1818
Annotates each line in the given file with information from the revision which
1919
last modified the line. Optionally, start annotating from the given revision.
2020

21-
The command can also limit the range of lines annotated.
21+
When specified one or more times, `-L` restricts annotation to the requested
22+
lines.
2223

2324
The origin of lines is automatically followed across whole-file
2425
renames (currently there is no option to turn the rename-following
@@ -130,7 +131,10 @@ SPECIFYING RANGES
130131

131132
Unlike 'git blame' and 'git annotate' in older versions of git, the extent
132133
of the annotation can be limited to both line ranges and revision
133-
ranges. When you are interested in finding the origin for
134+
ranges. The `-L` option, which limits annotation to a range of lines, may be
135+
specified multiple times.
136+
137+
When you are interested in finding the origin for
134138
lines 40-60 for file `foo`, you can use the `-L` option like so
135139
(they mean the same thing -- both ask for 21 lines starting at
136140
line 40):

Documentation/git-log.txt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ produced by --stat etc.
6262
Note that only message is considered, if also a diff is shown
6363
its size is not included.
6464

65-
-L <start>,<end>:<file>, -L :<regex>:<file>::
65+
-L <start>,<end>:<file>::
66+
-L :<regex>:<file>::
6667

6768
Trace the evolution of the line range given by "<start>,<end>"
6869
(or the funcname regex <regex>) within the <file>. You may
@@ -71,8 +72,6 @@ produced by --stat etc.
7172
give zero or one positive revision arguments.
7273
You can specify this option more than once.
7374
+
74-
<start> and <end> can take one of these forms:
75-
7675
include::line-range-format.txt[]
7776

7877
<revision range>::

Documentation/line-range-format.txt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
<start> and <end> can take one of these forms:
2+
13
- number
24
+
35
If <start> or <end> is a number, it specifies an
@@ -7,19 +9,21 @@ absolute line number (lines count from 1).
79
- /regex/
810
+
911
This form will use the first line matching the given
10-
POSIX regex. If <end> is a regex, it will search
12+
POSIX regex. If <start> is a regex, it will search from the end of
13+
the previous `-L` range, if any, otherwise from the start of file.
14+
If <start> is ``^/regex/'', it will search from the start of file.
15+
If <end> is a regex, it will search
1116
starting at the line given by <start>.
1217
+
1318

1419
- +offset or -offset
1520
+
1621
This is only valid for <end> and will specify a number
1722
of lines before or after the line given by <start>.
18-
+
1923

20-
- :regex
2124
+
22-
If the option's argument is of the form :regex, it denotes the range
25+
If ``:<regex>'' is given in place of <start> and <end>, it denotes the range
2326
from the first funcname line that matches <regex>, up to the next
24-
funcname line.
25-
+
27+
funcname line. ``:<regex>'' searches from the end of the previous `-L` range,
28+
if any, otherwise from the start of file.
29+
``^:<regex>'' searches from the start of file.

builtin/blame.c

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "utf8.h"
2323
#include "userdiff.h"
2424
#include "line-range.h"
25+
#include "line-log.h"
2526

2627
static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
2728

@@ -1937,18 +1938,6 @@ static const char *add_prefix(const char *prefix, const char *path)
19371938
return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
19381939
}
19391940

1940-
/*
1941-
* Parsing of -L option
1942-
*/
1943-
static void prepare_blame_range(struct scoreboard *sb,
1944-
const char *bottomtop,
1945-
long lno,
1946-
long *bottom, long *top)
1947-
{
1948-
if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))
1949-
usage(blame_usage);
1950-
}
1951-
19521941
static int git_blame_config(const char *var, const char *value, void *cb)
19531942
{
19541943
if (!strcmp(var, "blame.showroot")) {
@@ -2245,29 +2234,18 @@ static int blame_move_callback(const struct option *option, const char *arg, int
22452234
return 0;
22462235
}
22472236

2248-
static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
2249-
{
2250-
const char **bottomtop = option->value;
2251-
if (!arg)
2252-
return -1;
2253-
if (*bottomtop)
2254-
die("More than one '-L n,m' option given");
2255-
*bottomtop = arg;
2256-
return 0;
2257-
}
2258-
22592237
int cmd_blame(int argc, const char **argv, const char *prefix)
22602238
{
22612239
struct rev_info revs;
22622240
const char *path;
22632241
struct scoreboard sb;
22642242
struct origin *o;
2265-
struct blame_entry *ent;
2266-
long dashdash_pos, bottom, top, lno;
2243+
struct blame_entry *ent = NULL;
2244+
long dashdash_pos, lno;
22672245
const char *final_commit_name = NULL;
22682246
enum object_type type;
22692247

2270-
static const char *bottomtop = NULL;
2248+
static struct string_list range_list;
22712249
static int output_option = 0, opt = 0;
22722250
static int show_stats = 0;
22732251
static const char *revs_file = NULL;
@@ -2293,13 +2271,16 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
22932271
OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
22942272
{ OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback },
22952273
{ OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback },
2296-
OPT_CALLBACK('L', NULL, &bottomtop, N_("n,m"), N_("Process only line range n,m, counting from 1"), blame_bottomtop_callback),
2274+
OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")),
22972275
OPT__ABBREV(&abbrev),
22982276
OPT_END()
22992277
};
23002278

23012279
struct parse_opt_ctx_t ctx;
23022280
int cmd_is_annotate = !strcmp(argv[0], "annotate");
2281+
struct range_set ranges;
2282+
unsigned int range_i;
2283+
long anchor;
23032284

23042285
git_config(git_blame_config, NULL);
23052286
init_revisions(&revs, NULL);
@@ -2492,22 +2473,48 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
24922473
num_read_blob++;
24932474
lno = prepare_lines(&sb);
24942475

2495-
bottom = top = 0;
2496-
if (bottomtop)
2497-
prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
2498-
if (lno < top || ((lno || bottom) && lno < bottom))
2499-
die("file %s has only %lu lines", path, lno);
2500-
if (bottom < 1)
2501-
bottom = 1;
2502-
if (top < 1)
2503-
top = lno;
2504-
bottom--;
2505-
2506-
ent = xcalloc(1, sizeof(*ent));
2507-
ent->lno = bottom;
2508-
ent->num_lines = top - bottom;
2509-
ent->suspect = o;
2510-
ent->s_lno = bottom;
2476+
if (lno && !range_list.nr)
2477+
string_list_append(&range_list, xstrdup("1"));
2478+
2479+
anchor = 1;
2480+
range_set_init(&ranges, range_list.nr);
2481+
for (range_i = 0; range_i < range_list.nr; ++range_i) {
2482+
long bottom, top;
2483+
if (parse_range_arg(range_list.items[range_i].string,
2484+
nth_line_cb, &sb, lno, anchor,
2485+
&bottom, &top, sb.path))
2486+
usage(blame_usage);
2487+
if (lno < top || ((lno || bottom) && lno < bottom))
2488+
die("file %s has only %lu lines", path, lno);
2489+
if (bottom < 1)
2490+
bottom = 1;
2491+
if (top < 1)
2492+
top = lno;
2493+
bottom--;
2494+
range_set_append_unsafe(&ranges, bottom, top);
2495+
anchor = top + 1;
2496+
}
2497+
sort_and_merge_range_set(&ranges);
2498+
2499+
for (range_i = ranges.nr; range_i > 0; --range_i) {
2500+
const struct range *r = &ranges.ranges[range_i - 1];
2501+
long bottom = r->start;
2502+
long top = r->end;
2503+
struct blame_entry *next = ent;
2504+
ent = xcalloc(1, sizeof(*ent));
2505+
ent->lno = bottom;
2506+
ent->num_lines = top - bottom;
2507+
ent->suspect = o;
2508+
ent->s_lno = bottom;
2509+
ent->next = next;
2510+
if (next)
2511+
next->prev = ent;
2512+
origin_incref(o);
2513+
}
2514+
origin_decref(o);
2515+
2516+
range_set_release(&ranges);
2517+
string_list_clear(&range_list, 0);
25112518

25122519
sb.ent = ent;
25132520
sb.path = path;

line-log.c

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ static void range_set_grow(struct range_set *rs, size_t extra)
2323
/* Either initialization would be fine */
2424
#define RANGE_SET_INIT {0}
2525

26-
static void range_set_init(struct range_set *rs, size_t prealloc)
26+
void range_set_init(struct range_set *rs, size_t prealloc)
2727
{
2828
rs->alloc = rs->nr = 0;
2929
rs->ranges = NULL;
3030
if (prealloc)
3131
range_set_grow(rs, prealloc);
3232
}
3333

34-
static void range_set_release(struct range_set *rs)
34+
void range_set_release(struct range_set *rs)
3535
{
3636
free(rs->ranges);
3737
rs->alloc = rs->nr = 0;
@@ -56,7 +56,7 @@ static void range_set_move(struct range_set *dst, struct range_set *src)
5656
}
5757

5858
/* tack on a _new_ range _at the end_ */
59-
static void range_set_append_unsafe(struct range_set *rs, long a, long b)
59+
void range_set_append_unsafe(struct range_set *rs, long a, long b)
6060
{
6161
assert(a <= b);
6262
range_set_grow(rs, 1);
@@ -65,7 +65,7 @@ static void range_set_append_unsafe(struct range_set *rs, long a, long b)
6565
rs->nr++;
6666
}
6767

68-
static void range_set_append(struct range_set *rs, long a, long b)
68+
void range_set_append(struct range_set *rs, long a, long b)
6969
{
7070
assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);
7171
range_set_append_unsafe(rs, a, b);
@@ -107,7 +107,7 @@ static void range_set_check_invariants(struct range_set *rs)
107107
* In-place pass of sorting and merging the ranges in the range set,
108108
* to establish the invariants when we get the ranges from the user
109109
*/
110-
static void sort_and_merge_range_set(struct range_set *rs)
110+
void sort_and_merge_range_set(struct range_set *rs)
111111
{
112112
int i;
113113
int o = 0; /* output cursor */
@@ -291,15 +291,13 @@ static void line_log_data_insert(struct line_log_data **list,
291291

292292
if (p) {
293293
range_set_append_unsafe(&p->ranges, begin, end);
294-
sort_and_merge_range_set(&p->ranges);
295294
free(path);
296295
return;
297296
}
298297

299298
p = xcalloc(1, sizeof(struct line_log_data));
300299
p->path = path;
301300
range_set_append(&p->ranges, begin, end);
302-
sort_and_merge_range_set(&p->ranges);
303301
if (ip) {
304302
p->next = ip->next;
305303
ip->next = p;
@@ -566,12 +564,14 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
566564
struct nth_line_cb cb_data;
567565
struct string_list_item *item;
568566
struct line_log_data *ranges = NULL;
567+
struct line_log_data *p;
569568

570569
for_each_string_list_item(item, args) {
571570
const char *name_part, *range_part;
572571
char *full_name;
573572
struct diff_filespec *spec;
574573
long begin = 0, end = 0;
574+
long anchor;
575575

576576
name_part = skip_range_arg(item->string);
577577
if (!name_part || *name_part != ':' || !name_part[1])
@@ -590,8 +590,14 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
590590
cb_data.lines = lines;
591591
cb_data.line_ends = ends;
592592

593+
p = search_line_log_data(ranges, full_name, NULL);
594+
if (p && p->ranges.nr)
595+
anchor = p->ranges.ranges[p->ranges.nr - 1].end + 1;
596+
else
597+
anchor = 1;
598+
593599
if (parse_range_arg(range_part, nth_line, &cb_data,
594-
lines, &begin, &end,
600+
lines, anchor, &begin, &end,
595601
full_name))
596602
die("malformed -L argument '%s'", range_part);
597603
if (lines < end || ((lines || begin) && lines < begin))
@@ -608,6 +614,9 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
608614
ends = NULL;
609615
}
610616

617+
for (p = ranges; p; p = p->next)
618+
sort_and_merge_range_set(&p->ranges);
619+
611620
return ranges;
612621
}
613622

line-log.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ struct diff_ranges {
2525
struct range_set target;
2626
};
2727

28+
extern void range_set_init(struct range_set *, size_t prealloc);
29+
extern void range_set_release(struct range_set *);
30+
/* Range includes start; excludes end */
31+
extern void range_set_append_unsafe(struct range_set *, long start, long end);
32+
/* New range must begin at or after end of last added range */
33+
extern void range_set_append(struct range_set *, long start, long end);
34+
/*
35+
* In-place pass of sorting and merging the ranges in the range set,
36+
* to sort and make the ranges disjoint.
37+
*/
38+
extern void sort_and_merge_range_set(struct range_set *);
39+
2840
/* Linked list of interesting files and their associated ranges. The
2941
* list must be kept sorted by path.
3042
*

0 commit comments

Comments
 (0)