git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/16] blame: accept multiple -L ranges
@ 2013-08-06 13:59 Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 01/16] git-log.txt: place each -L option variation on its own line Eric Sunshine
                   ` (15 more replies)
  0 siblings, 16 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

This is a re-roll of [1] which teaches git-blame to accept multiple -L
ranges. It is built atop [6] (es/blame-L-more in 'pu').

The series is longer than expected since it includes a few more cleanup
patches beyond those already posted separately [2], [3], [4], [5], [6];
and because it implements relative /RE/ searches requested by Junio [7],
and brings git-log's multiple -L behavior in line with git-blame's.
Despite the length of the series, the patches are mostly small and
simple.


Changes since v1:

* Make /RE/ searches relative to end of previous -L, if any [7]. Ditto
  for :RE searches.

* Introduce ^/RE/ and ^:RE to search from start of file.

* Add (lots of) tests.

* Update documentation.

* Collect ranges via 'struct range_set' rather than (ab)using
  blame.c:coalesce() and add_blame_range().


Quick overview of patches:

1-2: More cleanups akin to [2], [3], [4], [5], [6].

3-7: Implement git-blame multiple -L support; add tests; update
  documentation.

8-14: Make /RE/ and :RE searches relative; introduce ^/RE/ and ^:RE to
  search from start of file; add tests; update documentation.

15-16: Reject bogus -L inputs for line numbers less than 1. This comes
  late in the series because this case becomes much easier to detect
  following patch 8.


[1]: http://thread.gmane.org/gmane.comp.version-control.git/229755
[2]: http://thread.gmane.org/gmane.comp.version-control.git/229917
[3]: http://thread.gmane.org/gmane.comp.version-control.git/230532
[4]: http://git.661346.n2.nabble.com/PATCH-0-6-fix-blame-L-regression-add-tests-tp7592174.html
[5]: http://thread.gmane.org/gmane.comp.version-control.git/231035
[6]: http://thread.gmane.org/gmane.comp.version-control.git/231412
[7]: http://article.gmane.org/gmane.comp.version-control.git/229966


Eric Sunshine (16):
  git-log.txt: place each -L option variation on its own line
  line-range-format.txt: clarify -L:regex usage form
  range-set: publish API for re-use by git-blame -L
  blame: inline one-line function into its lone caller
  blame: accept multiple -L ranges
  t8001/t8002: blame: add tests of multiple -L options
  blame: document multiple -L support
  line-range: teach -L/RE/ to search relative to anchor point
  blame: teach -L/RE/ to search from end of previous -L range
  log: teach -L/RE/ to search from end of previous -L range
  line-range-format.txt: document -L/RE/ relative search
  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
  t8001/t8002: blame: add tests of -L line numbers less than 1
  line-range: reject -L line numbers less than 1

 Documentation/blame-options.txt     |  10 +--
 Documentation/git-blame.txt         |  10 ++-
 Documentation/git-log.txt           |   5 +-
 Documentation/line-range-format.txt |  16 +++--
 builtin/blame.c                     |  93 ++++++++++++++------------
 line-log.c                          |  25 ++++---
 line-log.h                          |  12 ++++
 line-range.c                        |  62 +++++++++++++----
 line-range.h                        |   5 +-
 t/annotate-tests.sh                 | 130 ++++++++++++++++++++++++++++++++++--
 t/t4211-line-log.sh                 |   2 +-
 11 files changed, 282 insertions(+), 88 deletions(-)

-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v2 01/16] git-log.txt: place each -L option variation on its own line
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 02/16] line-range-format.txt: clarify -L:regex usage form Eric Sunshine
                   ` (14 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

Standard practice in Git documentation is for each variation of an
option (such as: -p / --porcelain) to be placed on its own line in the
OPTIONS table. The -L option does not follow suit. It cuddles "-L
<start>,<end>:<file>" and "-L :<regex>:<file>", separated by a comma.
This is inconsistent and potentially confusing since the comma
separating them is typeset the same as the comma in "<start>,<end>". Fix
this by placing each variation on its own line.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 Documentation/git-log.txt | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index ac2694d..fe04e06 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -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
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 02/16] line-range-format.txt: clarify -L:regex usage form
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 01/16] git-log.txt: place each -L option variation on its own line Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 03/16] range-set: publish API for re-use by git-blame -L Eric Sunshine
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

blame/log documentation describes -L option as:

  -L<start>,<end>
  -L:<regex>

  <start> and <end> can take one of these forms:

    * number
    * /regex/
    * +offset or -offset
    * :regex

which is incorrect and confusing since :regex is not one of the valid
forms of <start> or <end>; in fact, it must be -L's lone argument.

Clarify by discussing :<regex> at the same indentation level as "<start>
and <end>...":

  -L<start>,<end>
  -L:<regex>

  <start> and <end> can take one of these forms:

    * number
    * /regex/
    * +offset or -offset

  If :<regex> is given in place of <start> and <end> ...

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 Documentation/blame-options.txt     | 2 --
 Documentation/git-log.txt           | 2 --
 Documentation/line-range-format.txt | 7 +++----
 3 files changed, 3 insertions(+), 8 deletions(-)

diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index 4e55b15..489032c 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -15,8 +15,6 @@
 	``-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::
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index fe04e06..34097ef 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -72,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>::
diff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt
index 3e7ce72..a1b2f4e 100644
--- a/Documentation/line-range-format.txt
+++ b/Documentation/line-range-format.txt
@@ -1,3 +1,5 @@
+<start> and <end> can take one of these forms:
+
 - number
 +
 If <start> or <end> is a number, it specifies an
@@ -15,11 +17,8 @@ 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.
-+
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 03/16] range-set: publish API for re-use by git-blame -L
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 01/16] git-log.txt: place each -L option variation on its own line Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 02/16] line-range-format.txt: clarify -L:regex usage form Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 04/16] blame: inline one-line function into its lone caller Eric Sunshine
                   ` (12 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

git-blame is slated to accept multiple -L ranges.  git-log already
accepts multiple -L's but its implementation of range-set, which
organizes and normalizes -L ranges, is private.  Publish the small
subset of range-set API which is needed for git-blame multiple -L
support.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---

Since range-set is generic -- not at all specific to -L line ranges -- a
future patch series may want to publish the entire API, move the
implementation to its own file, and add documentation and full unit-test
coverage, however, such a topic is orthogonal to the present series
which adds multiple -L support to git-blame.

 line-log.c | 10 +++++-----
 line-log.h | 12 ++++++++++++
 2 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/line-log.c b/line-log.c
index 1c3ac8d..bdadf35 100644
--- a/line-log.c
+++ b/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 */
diff --git a/line-log.h b/line-log.h
index 8bea45f..a9212d8 100644
--- a/line-log.h
+++ b/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.
  *
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 04/16] blame: inline one-line function into its lone caller
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (2 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 03/16] range-set: publish API for re-use by git-blame -L Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 05/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

As of 25ed3412 (Refactor parse_loc; 2013-03-28),
blame.c:prepare_blame_range() became effectively a one-line function
which merely passes its arguments along to another function. This
indirection does not bring clarity to the code. Simplify by inlining
prepare_blame_range() into its lone caller.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 builtin/blame.c | 17 +++--------------
 1 file changed, 3 insertions(+), 14 deletions(-)

diff --git a/builtin/blame.c b/builtin/blame.c
index e70b089..9db01b0 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -1937,18 +1937,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")) {
@@ -2493,8 +2481,9 @@ parse_done:
 	lno = prepare_lines(&sb);
 
 	bottom = top = 0;
-	if (bottomtop)
-		prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
+	if (bottomtop && parse_range_arg(bottomtop, nth_line_cb, &sb, lno,
+					 &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)
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 05/16] blame: accept multiple -L ranges
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (3 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 04/16] blame: inline one-line function into its lone caller Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 21:33   ` Junio C Hamano
  2013-08-06 13:59 ` [PATCH v2 06/16] t8001/t8002: blame: add tests of multiple -L options Eric Sunshine
                   ` (10 subsequent siblings)
  15 siblings, 1 reply; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

git-blame accepts only a single -L option or none. Clients requiring
blame information for multiple disjoint ranges are therefore forced
either to invoke git-blame multiple times, once for each range, or only
once with no -L option to cover the entire file, both of which can be
costly.  Teach git-blame to accept multiple -L ranges.  Overlapping and
out-of-order ranges are accepted.

In this patch, the X in -LX,Y is absolute (for instance, /RE/ patterns
search from line 1), and Y is relative to X. Follow-up patches provide
more flexibility over how X is anchored.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---

This patch is a bit less scary when whitespace changes are ignored.

 builtin/blame.c | 79 ++++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 47 insertions(+), 32 deletions(-)

diff --git a/builtin/blame.c b/builtin/blame.c
index 9db01b0..2f4d9e2 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -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");
 
@@ -2233,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;
@@ -2281,13 +2271,15 @@ 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;
 
 	git_config(git_blame_config, NULL);
 	init_revisions(&revs, NULL);
@@ -2480,23 +2472,46 @@ parse_done:
 	num_read_blob++;
 	lno = prepare_lines(&sb);
 
-	bottom = top = 0;
-	if (bottomtop && parse_range_arg(bottomtop, nth_line_cb, &sb, lno,
-					 &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--;
-
-	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"));
+
+	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,
+				    &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);
+	}
+	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;
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 06/16] t8001/t8002: blame: add tests of multiple -L options
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (4 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 05/16] blame: accept multiple -L ranges Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 07/16] blame: document multiple -L support Eric Sunshine
                   ` (9 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 t/annotate-tests.sh | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index ce5b8ed..77083d9 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -271,6 +271,38 @@ 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 'setup -L :regex' '
 	tr Q "\\t" >hello.c <<-\EOF &&
 	int main(int argc, const char *argv[])
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 07/16] blame: document multiple -L support
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (5 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 06/16] t8001/t8002: blame: add tests of multiple -L options Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 08/16] line-range: teach -L/RE/ to search relative to anchor point Eric Sunshine
                   ` (8 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 Documentation/blame-options.txt |  8 +++++---
 Documentation/git-blame.txt     | 10 +++++++---
 2 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index 489032c..0cebc4f 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -11,9 +11,11 @@
 
 -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>.
 +
 include::line-range-format.txt[]
 
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
index 6cea7f1..f2c85cc 100644
--- a/Documentation/git-blame.txt
+++ b/Documentation/git-blame.txt
@@ -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):
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 08/16] line-range: teach -L/RE/ to search relative to anchor point
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (6 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 07/16] blame: document multiple -L support Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 21:44   ` Junio C Hamano
  2013-08-06 13:59 ` [PATCH v2 09/16] blame: teach -L/RE/ to search from end of previous -L range Eric Sunshine
                   ` (7 subsequent siblings)
  15 siblings, 1 reply; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

Range specification -L/RE/ for blame/log unconditionally begins
searching at line one. Mailing list discussion [1] suggests that, in the
presence of multiple -L options, -L/RE/ should search relative to the
endpoint of the previous -L range, if any.

Teach the parsing machinery underlying blame's and log's -L options to
accept a start point for -L/RE/ searches. Follow-up patches will upgrade
blame and log to take advantage of this ability.

[1]: http://thread.gmane.org/gmane.comp.version-control.git/229755/focus=229966

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 builtin/blame.c |  2 +-
 line-log.c      |  2 +-
 line-range.c    | 30 ++++++++++++++++++++++++++----
 line-range.h    |  5 ++++-
 4 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/builtin/blame.c b/builtin/blame.c
index 2f4d9e2..7b084d8 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -2479,7 +2479,7 @@ parse_done:
 	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,
+				    nth_line_cb, &sb, lno, 1,
 				    &bottom, &top, sb.path))
 			usage(blame_usage);
 		if (lno < top || ((lno || bottom) && lno < bottom))
diff --git a/line-log.c b/line-log.c
index bdadf35..38f827b 100644
--- a/line-log.c
+++ b/line-log.c
@@ -591,7 +591,7 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
 		cb_data.line_ends = ends;
 
 		if (parse_range_arg(range_part, nth_line, &cb_data,
-				    lines, &begin, &end,
+				    lines, 1, &begin, &end,
 				    full_name))
 			die("malformed -L argument '%s'", range_part);
 		if (lines < end || ((lines || begin) && lines < begin))
diff --git a/line-range.c b/line-range.c
index 69e8d6b..bbf3c0f 100644
--- a/line-range.c
+++ b/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)
@@ -46,6 +58,10 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
 			*ret = num;
 		return term;
 	}
+
+	if (begin < 0)
+		begin = -begin;
+
 	if (spec[0] != '/')
 		return spec;
 
@@ -85,7 +101,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);
 	}
 }
 
@@ -210,11 +227,16 @@ 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 (anchor < 1)
+		anchor = 1;
+	if (anchor > lines)
+		anchor = lines + 1;
+
 	if (*arg == ':') {
 		arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path);
 		if (!arg || *arg)
@@ -222,7 +244,7 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
 		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);
diff --git a/line-range.h b/line-range.h
index ae3d012..83ba3c2 100644
--- a/line-range.h
+++ b/line-range.h
@@ -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);
 
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 09/16] blame: teach -L/RE/ to search from end of previous -L range
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (7 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 08/16] line-range: teach -L/RE/ to search relative to anchor point Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 21:45   ` Junio C Hamano
  2013-08-06 13:59 ` [PATCH v2 10/16] log: " Eric Sunshine
                   ` (6 subsequent siblings)
  15 siblings, 1 reply; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 builtin/blame.c     |  5 ++++-
 t/annotate-tests.sh | 20 ++++++++++++++++++++
 2 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/builtin/blame.c b/builtin/blame.c
index 7b084d8..1bf8056 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -2280,6 +2280,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 	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);
@@ -2475,11 +2476,12 @@ parse_done:
 	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, 1,
+				    nth_line_cb, &sb, lno, anchor,
 				    &bottom, &top, sb.path))
 			usage(blame_usage);
 		if (lno < top || ((lno || bottom) && lno < bottom))
@@ -2490,6 +2492,7 @@ parse_done:
 			top = lno;
 		bottom--;
 		range_set_append_unsafe(&ranges, bottom, top);
+		anchor = top + 1;
 	}
 	sort_and_merge_range_set(&ranges);
 
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index 77083d9..b963d36 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -303,6 +303,26 @@ 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 'setup -L :regex' '
 	tr Q "\\t" >hello.c <<-\EOF &&
 	int main(int argc, const char *argv[])
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 10/16] log: teach -L/RE/ to search from end of previous -L range
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (8 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 09/16] blame: teach -L/RE/ to search from end of previous -L range Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 11/16] line-range-format.txt: document -L/RE/ relative search Eric Sunshine
                   ` (5 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

This is complicated slightly by having to remember the previous -L range
for each file specified via -L<range>:file.

The existing implementation coalesces ranges for each file as each -L is
parsed which makes it impossible to refer back to the previous -L range
for any particular file. Re-implement to instead store each file's set
of -L ranges verbatim, and then coalesce the ranges in a post-processing
step.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 line-log.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/line-log.c b/line-log.c
index 38f827b..d40c79d 100644
--- a/line-log.c
+++ b/line-log.c
@@ -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, 1, &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;
 }
 
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 11/16] line-range-format.txt: document -L/RE/ relative search
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (9 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 10/16] log: " Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 12/16] line-range: teach -L^/RE/ to search from start of file Eric Sunshine
                   ` (4 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

Option -L/RE/ of blame/log now searches relative to the previous -L
range, if any. Document this.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 Documentation/line-range-format.txt | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt
index a1b2f4e..42d74f7 100644
--- a/Documentation/line-range-format.txt
+++ b/Documentation/line-range-format.txt
@@ -9,7 +9,9 @@ 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 <end> is a regex, it will search
 starting at the line given by <start>.
 +
 
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 12/16] line-range: teach -L^/RE/ to search from start of file
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (10 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 11/16] line-range-format.txt: document -L/RE/ relative search Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 13/16] line-range: teach -L:RE to search from end of previous -L range Eric Sunshine
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

The -L/RE/ option of blame/log searches from the end of the previous -L
range, if any. Add new notation -L^/RE/ to override this behavior and
search from start of file.

The new ^/RE/ syntax is valid only as the <start> argument of
-L<start>,<end>. The <end> argument, as usual, is relative to <start>.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 Documentation/line-range-format.txt |  1 +
 line-range.c                        | 10 ++++++++--
 t/annotate-tests.sh                 | 21 +++++++++++++++++++++
 3 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt
index 42d74f7..cf84417 100644
--- a/Documentation/line-range-format.txt
+++ b/Documentation/line-range-format.txt
@@ -11,6 +11,7 @@ absolute line number (lines count from 1).
 This form will use the first line matching the given
 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>.
 +
diff --git a/line-range.c b/line-range.c
index bbf3c0f..7048489 100644
--- a/line-range.c
+++ b/line-range.c
@@ -59,8 +59,14 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
 		return term;
 	}
 
-	if (begin < 0)
-		begin = -begin;
+	if (begin < 0) {
+		if (spec[0] != '^')
+			begin = -begin;
+		else {
+			begin = 1;
+			spec++;
+		}
+	}
 
 	if (spec[0] != '/')
 		return spec;
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index b963d36..5a7d7c7 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -323,6 +323,23 @@ 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[])
@@ -464,3 +481,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
+'
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 13/16] line-range: teach -L:RE to search from end of previous -L range
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (11 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 12/16] line-range: teach -L^/RE/ to search from start of file Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 14/16] line-range: teach -L^:RE to search from start of file Eric Sunshine
                   ` (2 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

For consistency with -L/RE/, teach -L:RE to search relative to the end
of the previous -L range, if any.

The new behavior invalidates one test in t4211 which assumes that -L:RE
begins searching at start of file. This test will be resurrected in a
follow-up patch which teaches -L:RE how to override the default relative
search behavior.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 Documentation/line-range-format.txt |  3 ++-
 line-range.c                        | 12 +++++++-----
 t/annotate-tests.sh                 | 16 ++++++++++++++++
 t/t4211-line-log.sh                 |  1 -
 4 files changed, 25 insertions(+), 7 deletions(-)

diff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt
index cf84417..469d80b 100644
--- a/Documentation/line-range-format.txt
+++ b/Documentation/line-range-format.txt
@@ -24,4 +24,5 @@ of lines before or after the line given by <start>.
 +
 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.
diff --git a/line-range.c b/line-range.c
index 7048489..4bae4bf 100644
--- a/line-range.c
+++ b/line-range.c
@@ -161,7 +161,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;
@@ -187,7 +187,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) {
@@ -205,7 +206,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)++;
@@ -244,7 +246,7 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
 		anchor = lines + 1;
 
 	if (*arg == ':') {
-		arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path);
+		arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, anchor, begin, end, path);
 		if (!arg || *arg)
 			return -1;
 		return 0;
@@ -269,7 +271,7 @@ 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);
+		return parse_range_funcname(arg, NULL, NULL, 0, 0, NULL, NULL, NULL);
 
 	arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
 
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index 5a7d7c7..4f7d6ba 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -382,6 +382,22 @@ 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 'setup incremental' '
 	(
 	GIT_AUTHOR_NAME=I &&
diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh
index b01b3dd..8ba2d51 100755
--- a/t/t4211-line-log.sh
+++ b/t/t4211-line-log.sh
@@ -48,7 +48,6 @@ 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 :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
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 14/16] line-range: teach -L^:RE to search from start of file
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (12 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 13/16] line-range: teach -L:RE to search from end of previous -L range Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 15/16] t8001/t8002: blame: add tests of -L line numbers less than 1 Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 16/16] line-range: reject " Eric Sunshine
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

The -L:RE option of blame/log searches from the end of the previous -L
range, if any. Add new notation -L^:RE to override this behavior and
search from start of file.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 Documentation/line-range-format.txt |  1 +
 line-range.c                        |  9 +++++++--
 t/annotate-tests.sh                 | 17 +++++++++++++++++
 t/t4211-line-log.sh                 |  1 +
 4 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt
index 469d80b..d7f2603 100644
--- a/Documentation/line-range-format.txt
+++ b/Documentation/line-range-format.txt
@@ -26,3 +26,4 @@ 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. ``:<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.
diff --git a/line-range.c b/line-range.c
index 4bae4bf..ede0c6c 100644
--- a/line-range.c
+++ b/line-range.c
@@ -173,6 +173,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 != ':') {
@@ -245,7 +250,7 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
 	if (anchor > lines)
 		anchor = lines + 1;
 
-	if (*arg == ':') {
+	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;
@@ -270,7 +275,7 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
 
 const char *skip_range_arg(const char *arg)
 {
-	if (*arg == ':')
+	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);
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index 4f7d6ba..dabe89d 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -398,6 +398,23 @@ 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 &&
diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh
index 8ba2d51..7369d3c 100755
--- a/t/t4211-line-log.sh
+++ b/t/t4211-line-log.sh
@@ -48,6 +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 :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
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 15/16] t8001/t8002: blame: add tests of -L line numbers less than 1
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (13 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 14/16] line-range: teach -L^:RE to search from start of file Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  2013-08-06 13:59 ` [PATCH v2 16/16] line-range: reject " Eric Sunshine
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

git-blame -L is documented as accepting 1-based line numbers. When
handed a line number less than 1, -L's behavior is undocumented and
undefined; it's also nonsensical and should be rejected but is
nevertheless accepted. Demonstrate this shortcoming.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 t/annotate-tests.sh | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index dabe89d..376b042 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -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_failure 'blame -L -X' '
+	test_must_fail $PROG -L-1 file
+'
+
+test_expect_failure 'blame -L 0' '
+	test_must_fail $PROG -L0 file
+'
+
+test_expect_failure 'blame -L ,0' '
+	test_must_fail $PROG -L,0 file
+'
+
 test_expect_success 'blame -L ,+0' '
 	test_must_fail $PROG -L,+0 file
 '
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v2 16/16] line-range: reject -L line numbers less than 1
  2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
                   ` (14 preceding siblings ...)
  2013-08-06 13:59 ` [PATCH v2 15/16] t8001/t8002: blame: add tests of -L line numbers less than 1 Eric Sunshine
@ 2013-08-06 13:59 ` Eric Sunshine
  15 siblings, 0 replies; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 13:59 UTC (permalink / raw)
  To: git
  Cc: Eric Sunshine, Junio C Hamano, Thomas Rast, Jonathan Nieder,
	Michael Haggerty

Since inception, git-blame -L has been documented as accepting 1-based
line numbers. When handed a line number less than 1, -L's behavior is
undocumented and undefined; it's also nonsensical and should be
diagnosed as an error. Do so.

Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
---
 line-range.c        |  5 ++++-
 t/annotate-tests.sh | 18 +++++++++---------
 2 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/line-range.c b/line-range.c
index ede0c6c..de4e32f 100644
--- a/line-range.c
+++ b/line-range.c
@@ -54,8 +54,11 @@ 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;
 	}
 
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index 376b042..99caa42 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -185,15 +185,15 @@ test_expect_success 'blame -L Y,X (undocumented)' '
 	check_count -L6,3 B 1 B1 1 B2 1 D 1
 '
 
-test_expect_failure 'blame -L -X' '
+test_expect_success 'blame -L -X' '
 	test_must_fail $PROG -L-1 file
 '
 
-test_expect_failure 'blame -L 0' '
+test_expect_success 'blame -L 0' '
 	test_must_fail $PROG -L0 file
 '
 
-test_expect_failure 'blame -L ,0' '
+test_expect_success 'blame -L ,0' '
 	test_must_fail $PROG -L,0 file
 '
 
@@ -447,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' '
@@ -463,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' '
@@ -483,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' '
-- 
1.8.4.rc1.409.gbd48715

^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 05/16] blame: accept multiple -L ranges
  2013-08-06 13:59 ` [PATCH v2 05/16] blame: accept multiple -L ranges Eric Sunshine
@ 2013-08-06 21:33   ` Junio C Hamano
  2013-08-06 22:44     ` Eric Sunshine
  0 siblings, 1 reply; 22+ messages in thread
From: Junio C Hamano @ 2013-08-06 21:33 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: git, Thomas Rast, Jonathan Nieder, Michael Haggerty

> +	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);

Hmph, I do not see where the need for this decref is coming from.
Did we incref once too many somewhere?

> +	range_set_release(&ranges);
> +	string_list_clear(&range_list, 0);
>  
>  	sb.ent = ent;
>  	sb.path = path;

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 08/16] line-range: teach -L/RE/ to search relative to anchor point
  2013-08-06 13:59 ` [PATCH v2 08/16] line-range: teach -L/RE/ to search relative to anchor point Eric Sunshine
@ 2013-08-06 21:44   ` Junio C Hamano
  0 siblings, 0 replies; 22+ messages in thread
From: Junio C Hamano @ 2013-08-06 21:44 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: git, Thomas Rast, Jonathan Nieder, Michael Haggerty

Eric Sunshine <sunshine@sunshineco.com> writes:

> Range specification -L/RE/ for blame/log unconditionally begins
> searching at line one. Mailing list discussion [1] suggests that, in the
> presence of multiple -L options, -L/RE/ should search relative to the
> endpoint of the previous -L range, if any.
>
> Teach the parsing machinery underlying blame's and log's -L options to
> accept a start point for -L/RE/ searches. Follow-up patches will upgrade
> blame and log to take advantage of this ability.
>
> [1]: http://thread.gmane.org/gmane.comp.version-control.git/229755/focus=229966
>
> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
> ---
>  builtin/blame.c |  2 +-
>  line-log.c      |  2 +-
>  line-range.c    | 30 ++++++++++++++++++++++++++----
>  line-range.h    |  5 ++++-
>  4 files changed, 32 insertions(+), 7 deletions(-)
>
> diff --git a/builtin/blame.c b/builtin/blame.c
> index 2f4d9e2..7b084d8 100644
> --- a/builtin/blame.c
> +++ b/builtin/blame.c
> @@ -2479,7 +2479,7 @@ parse_done:
>  	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,
> +				    nth_line_cb, &sb, lno, 1,
>  				    &bottom, &top, sb.path))
>  			usage(blame_usage);
>  		if (lno < top || ((lno || bottom) && lno < bottom))
> diff --git a/line-log.c b/line-log.c
> index bdadf35..38f827b 100644
> --- a/line-log.c
> +++ b/line-log.c
> @@ -591,7 +591,7 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
>  		cb_data.line_ends = ends;
>  
>  		if (parse_range_arg(range_part, nth_line, &cb_data,
> -				    lines, &begin, &end,
> +				    lines, 1, &begin, &end,

This one feeds "1" to anchor, which in turn is given to parse_loc as
"-1" and then (after bypassing the <end> part support) its sign
flipped once again to become "begin=1" in parse_loc().  Then we run
regexp search starting from (1-based) 1st line, retaining the
"always scan from the beginning" behaviour in this step in the
series.

OK, looks sensible.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 09/16] blame: teach -L/RE/ to search from end of previous -L range
  2013-08-06 13:59 ` [PATCH v2 09/16] blame: teach -L/RE/ to search from end of previous -L range Eric Sunshine
@ 2013-08-06 21:45   ` Junio C Hamano
  0 siblings, 0 replies; 22+ messages in thread
From: Junio C Hamano @ 2013-08-06 21:45 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: git, Thomas Rast, Jonathan Nieder, Michael Haggerty

Eric Sunshine <sunshine@sunshineco.com> writes:

> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>
> ---

With the previous step, what this one does is fairly obvious and
straight-forward.  Looking good ;-)

>  builtin/blame.c     |  5 ++++-
>  t/annotate-tests.sh | 20 ++++++++++++++++++++
>  2 files changed, 24 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/blame.c b/builtin/blame.c
> index 7b084d8..1bf8056 100644
> --- a/builtin/blame.c
> +++ b/builtin/blame.c
> @@ -2280,6 +2280,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
>  	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);
> @@ -2475,11 +2476,12 @@ parse_done:
>  	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, 1,
> +				    nth_line_cb, &sb, lno, anchor,
>  				    &bottom, &top, sb.path))
>  			usage(blame_usage);
>  		if (lno < top || ((lno || bottom) && lno < bottom))
> @@ -2490,6 +2492,7 @@ parse_done:
>  			top = lno;
>  		bottom--;
>  		range_set_append_unsafe(&ranges, bottom, top);
> +		anchor = top + 1;
>  	}
>  	sort_and_merge_range_set(&ranges);
>  
> diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
> index 77083d9..b963d36 100644
> --- a/t/annotate-tests.sh
> +++ b/t/annotate-tests.sh
> @@ -303,6 +303,26 @@ 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 'setup -L :regex' '
>  	tr Q "\\t" >hello.c <<-\EOF &&
>  	int main(int argc, const char *argv[])

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 05/16] blame: accept multiple -L ranges
  2013-08-06 21:33   ` Junio C Hamano
@ 2013-08-06 22:44     ` Eric Sunshine
  2013-08-06 22:47       ` Junio C Hamano
  0 siblings, 1 reply; 22+ messages in thread
From: Eric Sunshine @ 2013-08-06 22:44 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git List, Thomas Rast, Jonathan Nieder, Michael Haggerty

On Tue, Aug 6, 2013 at 5:33 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> +     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);
>
> Hmph, I do not see where the need for this decref is coming from.
> Did we incref once too many somewhere?

Each constructed blame_entry must own a reference to the suspect.
o->refcnt should equal the number of blame_entries. At construction, a
'struct origin' has refcnt 1. In the original code, which supported
only a single initial range (blame_entry), we had:

  o = get-initial-suspect();  # refcnt already 1
  ent->suspect = o;  # refcnt still 1
  sb.ent = ent;
  assign_blame(&sb);

So, o->refcnt equals the number of blame_entries (1) when
assign_blame() is called.

The new for-loop calls origin_incref() on each iteration since each
blame_entry needs to own a reference to the suspect. Assume that we
have two disjoint -L ranges:

  o = get-initial-suspect();  # refcnt already 1
  foreach range:
    ent = new blame_entry;
    ent->suspect = o;
    origin_incref(o);  # refcnt++
  end
  # for 2 ranges, refcnt incremented twice, so value is 3
  origin_decref(o);  # refcnt = 2
  sb.ent = ent;
  assign_blame(&sb);

Thus, as with the original code, o->refcnt equals the number of
blame_entries (2) when assign_blame() is called.

The same holds for the boundary case when the file is empty and there
is no range. o->refcnt starts at 1, the loop is never entered so no
blame_entries are created, and o->refcnt gets decremented to 0, which
again matches the number of blame_entries (0).

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v2 05/16] blame: accept multiple -L ranges
  2013-08-06 22:44     ` Eric Sunshine
@ 2013-08-06 22:47       ` Junio C Hamano
  0 siblings, 0 replies; 22+ messages in thread
From: Junio C Hamano @ 2013-08-06 22:47 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Git List, Thomas Rast, Jonathan Nieder, Michael Haggerty

Eric Sunshine <sunshine@sunshineco.com> writes:

> Each constructed blame_entry must own a reference to the suspect.
> o->refcnt should equal the number of blame_entries. At construction, a
> 'struct origin' has refcnt 1. In the original code, which supported
> only a single initial range (blame_entry), we had:
>
>   o = get-initial-suspect();  # refcnt already 1
>   ent->suspect = o;  # refcnt still 1

Ah, of course.

I forgot that I initialized a new origin with refcnt 1 exactly for
this.  As you use it once for each range, you would need to
compensate for it.

Thanks.

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2013-08-06 22:47 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-08-06 13:59 [PATCH v2 00/16] blame: accept multiple -L ranges Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 01/16] git-log.txt: place each -L option variation on its own line Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 02/16] line-range-format.txt: clarify -L:regex usage form Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 03/16] range-set: publish API for re-use by git-blame -L Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 04/16] blame: inline one-line function into its lone caller Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 05/16] blame: accept multiple -L ranges Eric Sunshine
2013-08-06 21:33   ` Junio C Hamano
2013-08-06 22:44     ` Eric Sunshine
2013-08-06 22:47       ` Junio C Hamano
2013-08-06 13:59 ` [PATCH v2 06/16] t8001/t8002: blame: add tests of multiple -L options Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 07/16] blame: document multiple -L support Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 08/16] line-range: teach -L/RE/ to search relative to anchor point Eric Sunshine
2013-08-06 21:44   ` Junio C Hamano
2013-08-06 13:59 ` [PATCH v2 09/16] blame: teach -L/RE/ to search from end of previous -L range Eric Sunshine
2013-08-06 21:45   ` Junio C Hamano
2013-08-06 13:59 ` [PATCH v2 10/16] log: " Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 11/16] line-range-format.txt: document -L/RE/ relative search Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 12/16] line-range: teach -L^/RE/ to search from start of file Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 13/16] line-range: teach -L:RE to search from end of previous -L range Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 14/16] line-range: teach -L^:RE to search from start of file Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 15/16] t8001/t8002: blame: add tests of -L line numbers less than 1 Eric Sunshine
2013-08-06 13:59 ` [PATCH v2 16/16] line-range: reject " Eric Sunshine

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).