Skip to content

Commit

Permalink
Merge branch 'es/blame-L-twice'
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gitster committed Sep 9, 2013
2 parents 4ab4a6d + 5ce922a commit de9a253
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 88 deletions.
10 changes: 5 additions & 5 deletions Documentation/blame-options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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::
Expand Down
10 changes: 7 additions & 3 deletions Documentation/git-blame.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand Down
5 changes: 2 additions & 3 deletions Documentation/git-log.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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>::
Expand Down
16 changes: 10 additions & 6 deletions Documentation/line-range-format.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<start> and <end> can take one of these forms:

- number
+
If <start> or <end> is a number, it specifies an
Expand All @@ -7,19 +9,21 @@ 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>.
+

- +offset or -offset
+
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.
93 changes: 50 additions & 43 deletions builtin/blame.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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")) {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -2492,22 +2473,48 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
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--;

ent = xcalloc(1, sizeof(*ent));
ent->lno = bottom;
ent->num_lines = top - bottom;
ent->suspect = o;
ent->s_lno = bottom;
if (lno && !range_list.nr)
string_list_append(&range_list, xstrdup("1"));

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;
Expand Down
25 changes: 17 additions & 8 deletions line-log.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ 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;
if (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;
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -291,15 +291,13 @@ 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;
}

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;
Expand Down Expand Up @@ -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])
Expand All @@ -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))
Expand All @@ -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;
}

Expand Down
12 changes: 12 additions & 0 deletions line-log.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Loading

0 comments on commit de9a253

Please sign in to comment.