{"thread":{"id":"34624","subject":"[PATCH v2 00/16] blame: accept multiple -L ranges","startedAt":"2013-08-06T13:59:33Z","lastAt":"2013-08-06T22:47:44Z","messageCount":22,"participants":["Eric Sunshine","Junio C Hamano"],"isPatch":true,"patchVersion":2,"patchTotal":16},"messages":[{"id":"224664","messageId":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":null,"subject":"[PATCH v2 00/16] blame: accept multiple -L ranges","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:33Z","receivedAt":"2013-08-06T13:59:33Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This is a re-roll of [1] which teaches git-blame to accept multiple -L\nranges. It is built atop [6] (es/blame-L-more in 'pu').\n\nThe series is longer than expected since it includes a few more cleanup\npatches beyond those already posted separately [2], [3], [4], [5], [6];\nand because it implements relative /RE/ searches requested by Junio [7],\nand brings git-log's multiple -L behavior in line with git-blame's.\nDespite the length of the series, the patches are mostly small and\nsimple.\n\n\nChanges since v1:\n\n* Make /RE/ searches relative to end of previous -L, if any [7]. Ditto\n  for :RE searches.\n\n* Introduce ^/RE/ and ^:RE to search from start of file.\n\n* Add (lots of) tests.\n\n* Update documentation.\n\n* Collect ranges via 'struct range_set' rather than (ab)using\n  blame.c:coalesce() and add_blame_range().\n\n\nQuick overview of patches:\n\n1-2: More cleanups akin to [2], [3], [4], [5], [6].\n\n3-7: Implement git-blame multiple -L support; add tests; update\n  documentation.\n\n8-14: Make /RE/ and :RE searches relative; introduce ^/RE/ and ^:RE to\n  search from start of file; add tests; update documentation.\n\n15-16: Reject bogus -L inputs for line numbers less than 1. This comes\n  late in the series because this case becomes much easier to detect\n  following patch 8.\n\n\n[1]: http://thread.gmane.org/gmane.comp.version-control.git/229755\n[2]: http://thread.gmane.org/gmane.comp.version-control.git/229917\n[3]: http://thread.gmane.org/gmane.comp.version-control.git/230532\n[4]: http://git.661346.n2.nabble.com/PATCH-0-6-fix-blame-L-regression-add-tests-tp7592174.html\n[5]: http://thread.gmane.org/gmane.comp.version-control.git/231035\n[6]: http://thread.gmane.org/gmane.comp.version-control.git/231412\n[7]: http://article.gmane.org/gmane.comp.version-control.git/229966\n\n\nEric Sunshine (16):\n  git-log.txt: place each -L option variation on its own line\n  line-range-format.txt: clarify -L:regex usage form\n  range-set: publish API for re-use by git-blame -L\n  blame: inline one-line function into its lone caller\n  blame: accept multiple -L ranges\n  t8001/t8002: blame: add tests of multiple -L options\n  blame: document multiple -L support\n  line-range: teach -L/RE/ to search relative to anchor point\n  blame: teach -L/RE/ to search from end of previous -L range\n  log: teach -L/RE/ to search from end of previous -L range\n  line-range-format.txt: document -L/RE/ relative search\n  line-range: teach -L^/RE/ to search from start of file\n  line-range: teach -L:RE to search from end of previous -L range\n  line-range: teach -L^:RE to search from start of file\n  t8001/t8002: blame: add tests of -L line numbers less than 1\n  line-range: reject -L line numbers less than 1\n\n Documentation/blame-options.txt     |  10 +--\n Documentation/git-blame.txt         |  10 ++-\n Documentation/git-log.txt           |   5 +-\n Documentation/line-range-format.txt |  16 +++--\n builtin/blame.c                     |  93 ++++++++++++++------------\n line-log.c                          |  25 ++++---\n line-log.h                          |  12 ++++\n line-range.c                        |  62 +++++++++++++----\n line-range.h                        |   5 +-\n t/annotate-tests.sh                 | 130 ++++++++++++++++++++++++++++++++++--\n t/t4211-line-log.sh                 |   2 +-\n 11 files changed, 282 insertions(+), 88 deletions(-)\n\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224667","messageId":"1375797589-65308-2-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 01/16] git-log.txt: place each -L option variation on its own line","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:34Z","receivedAt":"2013-08-06T13:59:34Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Standard practice in Git documentation is for each variation of an\noption (such as: -p / --porcelain) to be placed on its own line in the\nOPTIONS table. The -L option does not follow suit. It cuddles \"-L\n<start>,<end>:<file>\" and \"-L :<regex>:<file>\", separated by a comma.\nThis is inconsistent and potentially confusing since the comma\nseparating them is typeset the same as the comma in \"<start>,<end>\". Fix\nthis by placing each variation on its own line.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/git-log.txt | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-log.txt b/Documentation/git-log.txt\nindex ac2694d..fe04e06 100644\n--- a/Documentation/git-log.txt\n+++ b/Documentation/git-log.txt\n@@ -62,7 +62,8 @@ produced by --stat etc.\n \tNote that only message is considered, if also a diff is shown\n \tits size is not included.\n \n--L <start>,<end>:<file>, -L :<regex>:<file>::\n+-L <start>,<end>:<file>::\n+-L :<regex>:<file>::\n \n \tTrace the evolution of the line range given by \"<start>,<end>\"\n \t(or the funcname regex <regex>) within the <file>.  You may\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224668","messageId":"1375797589-65308-3-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 02/16] line-range-format.txt: clarify -L:regex usage form","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:35Z","receivedAt":"2013-08-06T13:59:35Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"blame/log documentation describes -L option as:\n\n  -L<start>,<end>\n  -L:<regex>\n\n  <start> and <end> can take one of these forms:\n\n    * number\n    * /regex/\n    * +offset or -offset\n    * :regex\n\nwhich is incorrect and confusing since :regex is not one of the valid\nforms of <start> or <end>; in fact, it must be -L's lone argument.\n\nClarify by discussing :<regex> at the same indentation level as \"<start>\nand <end>...\":\n\n  -L<start>,<end>\n  -L:<regex>\n\n  <start> and <end> can take one of these forms:\n\n    * number\n    * /regex/\n    * +offset or -offset\n\n  If :<regex> is given in place of <start> and <end> ...\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/blame-options.txt     | 2 --\n Documentation/git-log.txt           | 2 --\n Documentation/line-range-format.txt | 7 +++----\n 3 files changed, 3 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt\nindex 4e55b15..489032c 100644\n--- a/Documentation/blame-options.txt\n+++ b/Documentation/blame-options.txt\n@@ -15,8 +15,6 @@\n \t``-L <start>'' or ``-L <start>,'' spans from <start> to end of file.\n \t``-L ,<end>'' spans from start of file to <end>.\n +\n-<start> and <end> can take one of these forms:\n-\n include::line-range-format.txt[]\n \n -l::\ndiff --git a/Documentation/git-log.txt b/Documentation/git-log.txt\nindex fe04e06..34097ef 100644\n--- a/Documentation/git-log.txt\n+++ b/Documentation/git-log.txt\n@@ -72,8 +72,6 @@ produced by --stat etc.\n \tgive zero or one positive revision arguments.\n \tYou can specify this option more than once.\n +\n-<start> and <end> can take one of these forms:\n-\n include::line-range-format.txt[]\n \n <revision range>::\ndiff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt\nindex 3e7ce72..a1b2f4e 100644\n--- a/Documentation/line-range-format.txt\n+++ b/Documentation/line-range-format.txt\n@@ -1,3 +1,5 @@\n+<start> and <end> can take one of these forms:\n+\n - number\n +\n If <start> or <end> is a number, it specifies an\n@@ -15,11 +17,8 @@ starting at the line given by <start>.\n +\n This is only valid for <end> and will specify a number\n of lines before or after the line given by <start>.\n-+\n \n-- :regex\n +\n-If the option's argument is of the form :regex, it denotes the range\n+If ``:<regex>'' is given in place of <start> and <end>, it denotes the range\n from the first funcname line that matches <regex>, up to the next\n funcname line.\n-+\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224666","messageId":"1375797589-65308-4-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 03/16] range-set: publish API for re-use by git-blame -L","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:36Z","receivedAt":"2013-08-06T13:59:36Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"git-blame is slated to accept multiple -L ranges.  git-log already\naccepts multiple -L's but its implementation of range-set, which\norganizes and normalizes -L ranges, is private.  Publish the small\nsubset of range-set API which is needed for git-blame multiple -L\nsupport.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n\nSince range-set is generic -- not at all specific to -L line ranges -- a\nfuture patch series may want to publish the entire API, move the\nimplementation to its own file, and add documentation and full unit-test\ncoverage, however, such a topic is orthogonal to the present series\nwhich adds multiple -L support to git-blame.\n\n line-log.c | 10 +++++-----\n line-log.h | 12 ++++++++++++\n 2 files changed, 17 insertions(+), 5 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex 1c3ac8d..bdadf35 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -23,7 +23,7 @@ static void range_set_grow(struct range_set *rs, size_t extra)\n /* Either initialization would be fine */\n #define RANGE_SET_INIT {0}\n \n-static void range_set_init(struct range_set *rs, size_t prealloc)\n+void range_set_init(struct range_set *rs, size_t prealloc)\n {\n \trs->alloc = rs->nr = 0;\n \trs->ranges = NULL;\n@@ -31,7 +31,7 @@ static void range_set_init(struct range_set *rs, size_t prealloc)\n \t\trange_set_grow(rs, prealloc);\n }\n \n-static void range_set_release(struct range_set *rs)\n+void range_set_release(struct range_set *rs)\n {\n \tfree(rs->ranges);\n \trs->alloc = rs->nr = 0;\n@@ -56,7 +56,7 @@ static void range_set_move(struct range_set *dst, struct range_set *src)\n }\n \n /* tack on a _new_ range _at the end_ */\n-static void range_set_append_unsafe(struct range_set *rs, long a, long b)\n+void range_set_append_unsafe(struct range_set *rs, long a, long b)\n {\n \tassert(a <= b);\n \trange_set_grow(rs, 1);\n@@ -65,7 +65,7 @@ static void range_set_append_unsafe(struct range_set *rs, long a, long b)\n \trs->nr++;\n }\n \n-static void range_set_append(struct range_set *rs, long a, long b)\n+void range_set_append(struct range_set *rs, long a, long b)\n {\n \tassert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);\n \trange_set_append_unsafe(rs, a, b);\n@@ -107,7 +107,7 @@ static void range_set_check_invariants(struct range_set *rs)\n  * In-place pass of sorting and merging the ranges in the range set,\n  * to establish the invariants when we get the ranges from the user\n  */\n-static void sort_and_merge_range_set(struct range_set *rs)\n+void sort_and_merge_range_set(struct range_set *rs)\n {\n \tint i;\n \tint o = 0; /* output cursor */\ndiff --git a/line-log.h b/line-log.h\nindex 8bea45f..a9212d8 100644\n--- a/line-log.h\n+++ b/line-log.h\n@@ -25,6 +25,18 @@ struct diff_ranges {\n \tstruct range_set target;\n };\n \n+extern void range_set_init(struct range_set *, size_t prealloc);\n+extern void range_set_release(struct range_set *);\n+/* Range includes start; excludes end */\n+extern void range_set_append_unsafe(struct range_set *, long start, long end);\n+/* New range must begin at or after end of last added range */\n+extern void range_set_append(struct range_set *, long start, long end);\n+/*\n+ * In-place pass of sorting and merging the ranges in the range set,\n+ * to sort and make the ranges disjoint.\n+ */\n+extern void sort_and_merge_range_set(struct range_set *);\n+\n /* Linked list of interesting files and their associated ranges.  The\n  * list must be kept sorted by path.\n  *\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224680","messageId":"1375797589-65308-5-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 04/16] blame: inline one-line function into its lone caller","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:37Z","receivedAt":"2013-08-06T13:59:37Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"As of 25ed3412 (Refactor parse_loc; 2013-03-28),\nblame.c:prepare_blame_range() became effectively a one-line function\nwhich merely passes its arguments along to another function. This\nindirection does not bring clarity to the code. Simplify by inlining\nprepare_blame_range() into its lone caller.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n builtin/blame.c | 17 +++--------------\n 1 file changed, 3 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex e70b089..9db01b0 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -1937,18 +1937,6 @@ static const char *add_prefix(const char *prefix, const char *path)\n \treturn prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n }\n \n-/*\n- * Parsing of -L option\n- */\n-static void prepare_blame_range(struct scoreboard *sb,\n-\t\t\t\tconst char *bottomtop,\n-\t\t\t\tlong lno,\n-\t\t\t\tlong *bottom, long *top)\n-{\n-\tif (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))\n-\t\tusage(blame_usage);\n-}\n-\n static int git_blame_config(const char *var, const char *value, void *cb)\n {\n \tif (!strcmp(var, \"blame.showroot\")) {\n@@ -2493,8 +2481,9 @@ parse_done:\n \tlno = prepare_lines(&sb);\n \n \tbottom = top = 0;\n-\tif (bottomtop)\n-\t\tprepare_blame_range(&sb, bottomtop, lno, &bottom, &top);\n+\tif (bottomtop && parse_range_arg(bottomtop, nth_line_cb, &sb, lno,\n+\t\t\t\t\t &bottom, &top, sb.path))\n+\t\tusage(blame_usage);\n \tif (lno < top || ((lno || bottom) && lno < bottom))\n \t\tdie(\"file %s has only %lu lines\", path, lno);\n \tif (bottom < 1)\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224665","messageId":"1375797589-65308-6-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 05/16] blame: accept multiple -L ranges","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:38Z","receivedAt":"2013-08-06T13:59:38Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"git-blame accepts only a single -L option or none. Clients requiring\nblame information for multiple disjoint ranges are therefore forced\neither to invoke git-blame multiple times, once for each range, or only\nonce with no -L option to cover the entire file, both of which can be\ncostly.  Teach git-blame to accept multiple -L ranges.  Overlapping and\nout-of-order ranges are accepted.\n\nIn this patch, the X in -LX,Y is absolute (for instance, /RE/ patterns\nsearch from line 1), and Y is relative to X. Follow-up patches provide\nmore flexibility over how X is anchored.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n\nThis patch is a bit less scary when whitespace changes are ignored.\n\n builtin/blame.c | 79 ++++++++++++++++++++++++++++++++++-----------------------\n 1 file changed, 47 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 9db01b0..2f4d9e2 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -22,6 +22,7 @@\n #include \"utf8.h\"\n #include \"userdiff.h\"\n #include \"line-range.h\"\n+#include \"line-log.h\"\n \n static char blame_usage[] = N_(\"git blame [options] [rev-opts] [rev] [--] file\");\n \n@@ -2233,29 +2234,18 @@ static int blame_move_callback(const struct option *option, const char *arg, int\n \treturn 0;\n }\n \n-static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)\n-{\n-\tconst char **bottomtop = option->value;\n-\tif (!arg)\n-\t\treturn -1;\n-\tif (*bottomtop)\n-\t\tdie(\"More than one '-L n,m' option given\");\n-\t*bottomtop = arg;\n-\treturn 0;\n-}\n-\n int cmd_blame(int argc, const char **argv, const char *prefix)\n {\n \tstruct rev_info revs;\n \tconst char *path;\n \tstruct scoreboard sb;\n \tstruct origin *o;\n-\tstruct blame_entry *ent;\n-\tlong dashdash_pos, bottom, top, lno;\n+\tstruct blame_entry *ent = NULL;\n+\tlong dashdash_pos, lno;\n \tconst char *final_commit_name = NULL;\n \tenum object_type type;\n \n-\tstatic const char *bottomtop = NULL;\n+\tstatic struct string_list range_list;\n \tstatic int output_option = 0, opt = 0;\n \tstatic int show_stats = 0;\n \tstatic const char *revs_file = NULL;\n@@ -2281,13 +2271,15 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"contents\", &contents_from, N_(\"file\"), N_(\"Use <file>'s contents as the final image\")),\n \t\t{ OPTION_CALLBACK, 'C', NULL, &opt, N_(\"score\"), N_(\"Find line copies within and across files\"), PARSE_OPT_OPTARG, blame_copy_callback },\n \t\t{ OPTION_CALLBACK, 'M', NULL, &opt, N_(\"score\"), N_(\"Find line movements within and across files\"), PARSE_OPT_OPTARG, blame_move_callback },\n-\t\tOPT_CALLBACK('L', NULL, &bottomtop, N_(\"n,m\"), N_(\"Process only line range n,m, counting from 1\"), blame_bottomtop_callback),\n+\t\tOPT_STRING_LIST('L', NULL, &range_list, N_(\"n,m\"), N_(\"Process only line range n,m, counting from 1\")),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_END()\n \t};\n \n \tstruct parse_opt_ctx_t ctx;\n \tint cmd_is_annotate = !strcmp(argv[0], \"annotate\");\n+\tstruct range_set ranges;\n+\tunsigned int range_i;\n \n \tgit_config(git_blame_config, NULL);\n \tinit_revisions(&revs, NULL);\n@@ -2480,23 +2472,46 @@ parse_done:\n \tnum_read_blob++;\n \tlno = prepare_lines(&sb);\n \n-\tbottom = top = 0;\n-\tif (bottomtop && parse_range_arg(bottomtop, nth_line_cb, &sb, lno,\n-\t\t\t\t\t &bottom, &top, sb.path))\n-\t\tusage(blame_usage);\n-\tif (lno < top || ((lno || bottom) && lno < bottom))\n-\t\tdie(\"file %s has only %lu lines\", path, lno);\n-\tif (bottom < 1)\n-\t\tbottom = 1;\n-\tif (top < 1)\n-\t\ttop = lno;\n-\tbottom--;\n-\n-\tent = xcalloc(1, sizeof(*ent));\n-\tent->lno = bottom;\n-\tent->num_lines = top - bottom;\n-\tent->suspect = o;\n-\tent->s_lno = bottom;\n+\tif (lno && !range_list.nr)\n+\t\tstring_list_append(&range_list, xstrdup(\"1\"));\n+\n+\trange_set_init(&ranges, range_list.nr);\n+\tfor (range_i = 0; range_i < range_list.nr; ++range_i) {\n+\t\tlong bottom, top;\n+\t\tif (parse_range_arg(range_list.items[range_i].string,\n+\t\t\t\t    nth_line_cb, &sb, lno,\n+\t\t\t\t    &bottom, &top, sb.path))\n+\t\t\tusage(blame_usage);\n+\t\tif (lno < top || ((lno || bottom) && lno < bottom))\n+\t\t\tdie(\"file %s has only %lu lines\", path, lno);\n+\t\tif (bottom < 1)\n+\t\t\tbottom = 1;\n+\t\tif (top < 1)\n+\t\t\ttop = lno;\n+\t\tbottom--;\n+\t\trange_set_append_unsafe(&ranges, bottom, top);\n+\t}\n+\tsort_and_merge_range_set(&ranges);\n+\n+\tfor (range_i = ranges.nr; range_i > 0; --range_i) {\n+\t\tconst struct range *r = &ranges.ranges[range_i - 1];\n+\t\tlong bottom = r->start;\n+\t\tlong top = r->end;\n+\t\tstruct blame_entry *next = ent;\n+\t\tent = xcalloc(1, sizeof(*ent));\n+\t\tent->lno = bottom;\n+\t\tent->num_lines = top - bottom;\n+\t\tent->suspect = o;\n+\t\tent->s_lno = bottom;\n+\t\tent->next = next;\n+\t\tif (next)\n+\t\t\tnext->prev = ent;\n+\t\torigin_incref(o);\n+\t}\n+\torigin_decref(o);\n+\n+\trange_set_release(&ranges);\n+\tstring_list_clear(&range_list, 0);\n \n \tsb.ent = ent;\n \tsb.path = path;\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224677","messageId":"1375797589-65308-7-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 06/16] t8001/t8002: blame: add tests of multiple -L options","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:39Z","receivedAt":"2013-08-06T13:59:39Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/annotate-tests.sh | 32 ++++++++++++++++++++++++++++++++\n 1 file changed, 32 insertions(+)\n\ndiff --git a/t/annotate-tests.sh b/t/annotate-tests.sh\nindex ce5b8ed..77083d9 100644\n--- a/t/annotate-tests.sh\n+++ b/t/annotate-tests.sh\n@@ -271,6 +271,38 @@ test_expect_success 'blame -L ,Y (Y > nlines)' '\n \ttest_must_fail $PROG -L,12345 file\n '\n \n+test_expect_success 'blame -L multiple (disjoint)' '\n+\tcheck_count -L2,3 -L6,7 A 1 B1 1 B2 1 \"A U Thor\" 1\n+'\n+\n+test_expect_success 'blame -L multiple (disjoint: unordered)' '\n+\tcheck_count -L6,7 -L2,3 A 1 B1 1 B2 1 \"A U Thor\" 1\n+'\n+\n+test_expect_success 'blame -L multiple (adjacent)' '\n+\tcheck_count -L2,3 -L4,5 A 1 B 1 B2 1 D 1\n+'\n+\n+test_expect_success 'blame -L multiple (adjacent: unordered)' '\n+\tcheck_count -L4,5 -L2,3 A 1 B 1 B2 1 D 1\n+'\n+\n+test_expect_success 'blame -L multiple (overlapping)' '\n+\tcheck_count -L2,4 -L3,5 A 1 B 1 B2 1 D 1\n+'\n+\n+test_expect_success 'blame -L multiple (overlapping: unordered)' '\n+\tcheck_count -L3,5 -L2,4 A 1 B 1 B2 1 D 1\n+'\n+\n+test_expect_success 'blame -L multiple (superset/subset)' '\n+\tcheck_count -L2,8 -L3,5 A 1 B 1 B1 1 B2 1 C 1 D 1 \"A U Thor\" 1\n+'\n+\n+test_expect_success 'blame -L multiple (superset/subset: unordered)' '\n+\tcheck_count -L3,5 -L2,8 A 1 B 1 B1 1 B2 1 C 1 D 1 \"A U Thor\" 1\n+'\n+\n test_expect_success 'setup -L :regex' '\n \ttr Q \"\\\\t\" >hello.c <<-\\EOF &&\n \tint main(int argc, const char *argv[])\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224672","messageId":"1375797589-65308-8-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 07/16] blame: document multiple -L support","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:40Z","receivedAt":"2013-08-06T13:59:40Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/blame-options.txt |  8 +++++---\n Documentation/git-blame.txt     | 10 +++++++---\n 2 files changed, 12 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt\nindex 489032c..0cebc4f 100644\n--- a/Documentation/blame-options.txt\n+++ b/Documentation/blame-options.txt\n@@ -11,9 +11,11 @@\n \n -L <start>,<end>::\n -L :<regex>::\n-\tAnnotate only the given line range.  <start> and <end> are optional.\n-\t``-L <start>'' or ``-L <start>,'' spans from <start> to end of file.\n-\t``-L ,<end>'' spans from start of file to <end>.\n+\tAnnotate only the given line range. May be specified multiple times.\n+\tOverlapping ranges are allowed.\n++\n+<start> and <end> are optional. ``-L <start>'' or ``-L <start>,'' spans from\n+<start> to end of file. ``-L ,<end>'' spans from start of file to <end>.\n +\n include::line-range-format.txt[]\n \ndiff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt\nindex 6cea7f1..f2c85cc 100644\n--- a/Documentation/git-blame.txt\n+++ b/Documentation/git-blame.txt\n@@ -9,7 +9,7 @@ SYNOPSIS\n --------\n [verse]\n 'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]\n-\t    [-L n,m | -L :fn] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]\n+\t    [-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]\n \t    [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file>\n \n DESCRIPTION\n@@ -18,7 +18,8 @@ DESCRIPTION\n Annotates each line in the given file with information from the revision which\n last modified the line. Optionally, start annotating from the given revision.\n \n-The command can also limit the range of lines annotated.\n+When specified one or more times, `-L` restricts annotation to the requested\n+lines.\n \n The origin of lines is automatically followed across whole-file\n renames (currently there is no option to turn the rename-following\n@@ -130,7 +131,10 @@ SPECIFYING RANGES\n \n Unlike 'git blame' and 'git annotate' in older versions of git, the extent\n of the annotation can be limited to both line ranges and revision\n-ranges.  When you are interested in finding the origin for\n+ranges. The `-L` option, which limits annotation to a range of lines, may be\n+specified multiple times.\n+\n+When you are interested in finding the origin for\n lines 40-60 for file `foo`, you can use the `-L` option like so\n (they mean the same thing -- both ask for 21 lines starting at\n line 40):\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224679","messageId":"1375797589-65308-9-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 08/16] line-range: teach -L/RE/ to search relative to anchor point","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:41Z","receivedAt":"2013-08-06T13:59:41Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Range specification -L/RE/ for blame/log unconditionally begins\nsearching at line one. Mailing list discussion [1] suggests that, in the\npresence of multiple -L options, -L/RE/ should search relative to the\nendpoint of the previous -L range, if any.\n\nTeach the parsing machinery underlying blame's and log's -L options to\naccept a start point for -L/RE/ searches. Follow-up patches will upgrade\nblame and log to take advantage of this ability.\n\n[1]: http://thread.gmane.org/gmane.comp.version-control.git/229755/focus=229966\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n builtin/blame.c |  2 +-\n line-log.c      |  2 +-\n line-range.c    | 30 ++++++++++++++++++++++++++----\n line-range.h    |  5 ++++-\n 4 files changed, 32 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 2f4d9e2..7b084d8 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -2479,7 +2479,7 @@ parse_done:\n \tfor (range_i = 0; range_i < range_list.nr; ++range_i) {\n \t\tlong bottom, top;\n \t\tif (parse_range_arg(range_list.items[range_i].string,\n-\t\t\t\t    nth_line_cb, &sb, lno,\n+\t\t\t\t    nth_line_cb, &sb, lno, 1,\n \t\t\t\t    &bottom, &top, sb.path))\n \t\t\tusage(blame_usage);\n \t\tif (lno < top || ((lno || bottom) && lno < bottom))\ndiff --git a/line-log.c b/line-log.c\nindex bdadf35..38f827b 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -591,7 +591,7 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)\n \t\tcb_data.line_ends = ends;\n \n \t\tif (parse_range_arg(range_part, nth_line, &cb_data,\n-\t\t\t\t    lines, &begin, &end,\n+\t\t\t\t    lines, 1, &begin, &end,\n \t\t\t\t    full_name))\n \t\t\tdie(\"malformed -L argument '%s'\", range_part);\n \t\tif (lines < end || ((lines || begin) && lines < begin))\ndiff --git a/line-range.c b/line-range.c\nindex 69e8d6b..bbf3c0f 100644\n--- a/line-range.c\n+++ b/line-range.c\n@@ -6,6 +6,18 @@\n \n /*\n  * Parse one item in the -L option\n+ *\n+ * 'begin' is applicable only to relative range anchors. Absolute anchors\n+ * ignore this value.\n+ *\n+ * When parsing \"-L A,B\", parse_loc() is called once for A and once for B.\n+ *\n+ * When parsing A, 'begin' must be a negative number, the absolute value of\n+ * which is the line at which relative start-of-range anchors should be\n+ * based. Beginning of file is represented by -1.\n+ *\n+ * When parsing B, 'begin' must be the positive line number immediately\n+ * following the line computed for 'A'.\n  */\n static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,\n \t\t\t     void *data, long lines, long begin, long *ret)\n@@ -46,6 +58,10 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,\n \t\t\t*ret = num;\n \t\treturn term;\n \t}\n+\n+\tif (begin < 0)\n+\t\tbegin = -begin;\n+\n \tif (spec[0] != '/')\n \t\treturn spec;\n \n@@ -85,7 +101,8 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,\n \telse {\n \t\tchar errbuf[1024];\n \t\tregerror(reg_error, &regexp, errbuf, 1024);\n-\t\tdie(\"-L parameter '%s': %s\", spec + 1, errbuf);\n+\t\tdie(\"-L parameter '%s' starting at line %ld: %s\",\n+\t\t    spec + 1, begin + 1, errbuf);\n \t}\n }\n \n@@ -210,11 +227,16 @@ static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_\n }\n \n int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,\n-\t\t    void *cb_data, long lines, long *begin, long *end,\n-\t\t    const char *path)\n+\t\t    void *cb_data, long lines, long anchor,\n+\t\t    long *begin, long *end, const char *path)\n {\n \t*begin = *end = 0;\n \n+\tif (anchor < 1)\n+\t\tanchor = 1;\n+\tif (anchor > lines)\n+\t\tanchor = lines + 1;\n+\n \tif (*arg == ':') {\n \t\targ = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path);\n \t\tif (!arg || *arg)\n@@ -222,7 +244,7 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,\n \t\treturn 0;\n \t}\n \n-\targ = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin);\n+\targ = parse_loc(arg, nth_line_cb, cb_data, lines, -anchor, begin);\n \n \tif (*arg == ',')\n \t\targ = parse_loc(arg + 1, nth_line_cb, cb_data, lines, *begin + 1, end);\ndiff --git a/line-range.h b/line-range.h\nindex ae3d012..83ba3c2 100644\n--- a/line-range.h\n+++ b/line-range.h\n@@ -9,6 +9,9 @@\n  * line 'lno' inside the 'cb_data'.  The caller is expected to already\n  * have a suitable map at hand to make this a constant-time lookup.\n  *\n+ * 'anchor' is the 1-based line at which relative range specifications\n+ * should be anchored. Absolute ranges are unaffected by this value.\n+ *\n  * Returns 0 in case of success and -1 if there was an error.  The\n  * actual range is stored in *begin and *end.  The counting starts\n  * at 1!  In case of error, the caller should show usage message.\n@@ -18,7 +21,7 @@ typedef const char *(*nth_line_fn_t)(void *data, long lno);\n \n extern int parse_range_arg(const char *arg,\n \t\t\t   nth_line_fn_t nth_line_cb,\n-\t\t\t   void *cb_data, long lines,\n+\t\t\t   void *cb_data, long lines, long anchor,\n \t\t\t   long *begin, long *end,\n \t\t\t   const char *path);\n \n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224669","messageId":"1375797589-65308-10-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 09/16] blame: teach -L/RE/ to search from end of previous -L range","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:42Z","receivedAt":"2013-08-06T13:59:42Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n builtin/blame.c     |  5 ++++-\n t/annotate-tests.sh | 20 ++++++++++++++++++++\n 2 files changed, 24 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 7b084d8..1bf8056 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -2280,6 +2280,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \tint cmd_is_annotate = !strcmp(argv[0], \"annotate\");\n \tstruct range_set ranges;\n \tunsigned int range_i;\n+\tlong anchor;\n \n \tgit_config(git_blame_config, NULL);\n \tinit_revisions(&revs, NULL);\n@@ -2475,11 +2476,12 @@ parse_done:\n \tif (lno && !range_list.nr)\n \t\tstring_list_append(&range_list, xstrdup(\"1\"));\n \n+\tanchor = 1;\n \trange_set_init(&ranges, range_list.nr);\n \tfor (range_i = 0; range_i < range_list.nr; ++range_i) {\n \t\tlong bottom, top;\n \t\tif (parse_range_arg(range_list.items[range_i].string,\n-\t\t\t\t    nth_line_cb, &sb, lno, 1,\n+\t\t\t\t    nth_line_cb, &sb, lno, anchor,\n \t\t\t\t    &bottom, &top, sb.path))\n \t\t\tusage(blame_usage);\n \t\tif (lno < top || ((lno || bottom) && lno < bottom))\n@@ -2490,6 +2492,7 @@ parse_done:\n \t\t\ttop = lno;\n \t\tbottom--;\n \t\trange_set_append_unsafe(&ranges, bottom, top);\n+\t\tanchor = top + 1;\n \t}\n \tsort_and_merge_range_set(&ranges);\n \ndiff --git a/t/annotate-tests.sh b/t/annotate-tests.sh\nindex 77083d9..b963d36 100644\n--- a/t/annotate-tests.sh\n+++ b/t/annotate-tests.sh\n@@ -303,6 +303,26 @@ test_expect_success 'blame -L multiple (superset/subset: unordered)' '\n \tcheck_count -L3,5 -L2,8 A 1 B 1 B1 1 B2 1 C 1 D 1 \"A U Thor\" 1\n '\n \n+test_expect_success 'blame -L /RE/ (relative)' '\n+\tcheck_count -L3,3 -L/fox/ B1 1 B2 1 C 1 D 1 \"A U Thor\" 1\n+'\n+\n+test_expect_success 'blame -L /RE/ (relative: no preceding range)' '\n+\tcheck_count -L/dog/ A 1 B 1 B1 1 B2 1 C 1 D 1 \"A U Thor\" 1\n+'\n+\n+test_expect_success 'blame -L /RE/ (relative: adjacent)' '\n+\tcheck_count -L1,1 -L/dog/,+1 A 1 E 1\n+'\n+\n+test_expect_success 'blame -L /RE/ (relative: not found)' '\n+\ttest_must_fail $PROG -L4,4 -L/dog/ file\n+'\n+\n+test_expect_success 'blame -L /RE/ (relative: end-of-file)' '\n+\ttest_must_fail $PROG -L, -L/$/ file\n+'\n+\n test_expect_success 'setup -L :regex' '\n \ttr Q \"\\\\t\" >hello.c <<-\\EOF &&\n \tint main(int argc, const char *argv[])\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224673","messageId":"1375797589-65308-11-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 10/16] log: teach -L/RE/ to search from end of previous -L range","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:43Z","receivedAt":"2013-08-06T13:59:43Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This is complicated slightly by having to remember the previous -L range\nfor each file specified via -L<range>:file.\n\nThe existing implementation coalesces ranges for each file as each -L is\nparsed which makes it impossible to refer back to the previous -L range\nfor any particular file. Re-implement to instead store each file's set\nof -L ranges verbatim, and then coalesce the ranges in a post-processing\nstep.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n line-log.c | 15 ++++++++++++---\n 1 file changed, 12 insertions(+), 3 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex 38f827b..d40c79d 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -291,7 +291,6 @@ static void line_log_data_insert(struct line_log_data **list,\n \n \tif (p) {\n \t\trange_set_append_unsafe(&p->ranges, begin, end);\n-\t\tsort_and_merge_range_set(&p->ranges);\n \t\tfree(path);\n \t\treturn;\n \t}\n@@ -299,7 +298,6 @@ static void line_log_data_insert(struct line_log_data **list,\n \tp = xcalloc(1, sizeof(struct line_log_data));\n \tp->path = path;\n \trange_set_append(&p->ranges, begin, end);\n-\tsort_and_merge_range_set(&p->ranges);\n \tif (ip) {\n \t\tp->next = ip->next;\n \t\tip->next = p;\n@@ -566,12 +564,14 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)\n \tstruct nth_line_cb cb_data;\n \tstruct string_list_item *item;\n \tstruct line_log_data *ranges = NULL;\n+\tstruct line_log_data *p;\n \n \tfor_each_string_list_item(item, args) {\n \t\tconst char *name_part, *range_part;\n \t\tchar *full_name;\n \t\tstruct diff_filespec *spec;\n \t\tlong begin = 0, end = 0;\n+\t\tlong anchor;\n \n \t\tname_part = skip_range_arg(item->string);\n \t\tif (!name_part || *name_part != ':' || !name_part[1])\n@@ -590,8 +590,14 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)\n \t\tcb_data.lines = lines;\n \t\tcb_data.line_ends = ends;\n \n+\t\tp = search_line_log_data(ranges, full_name, NULL);\n+\t\tif (p && p->ranges.nr)\n+\t\t\tanchor = p->ranges.ranges[p->ranges.nr - 1].end + 1;\n+\t\telse\n+\t\t\tanchor = 1;\n+\n \t\tif (parse_range_arg(range_part, nth_line, &cb_data,\n-\t\t\t\t    lines, 1, &begin, &end,\n+\t\t\t\t    lines, anchor, &begin, &end,\n \t\t\t\t    full_name))\n \t\t\tdie(\"malformed -L argument '%s'\", range_part);\n \t\tif (lines < end || ((lines || begin) && lines < begin))\n@@ -608,6 +614,9 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)\n \t\tends = NULL;\n \t}\n \n+\tfor (p = ranges; p; p = p->next)\n+\t\tsort_and_merge_range_set(&p->ranges);\n+\n \treturn ranges;\n }\n \n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224670","messageId":"1375797589-65308-12-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 11/16] line-range-format.txt: document -L/RE/ relative search","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:44Z","receivedAt":"2013-08-06T13:59:44Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Option -L/RE/ of blame/log now searches relative to the previous -L\nrange, if any. Document this.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/line-range-format.txt | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt\nindex a1b2f4e..42d74f7 100644\n--- a/Documentation/line-range-format.txt\n+++ b/Documentation/line-range-format.txt\n@@ -9,7 +9,9 @@ absolute line number (lines count from 1).\n - /regex/\n +\n This form will use the first line matching the given\n-POSIX regex.  If <end> is a regex, it will search\n+POSIX regex. If <start> is a regex, it will search from the end of\n+the previous `-L` range, if any, otherwise from the start of file.\n+If <end> is a regex, it will search\n starting at the line given by <start>.\n +\n \n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224671","messageId":"1375797589-65308-13-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 12/16] line-range: teach -L^/RE/ to search from start of file","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:45Z","receivedAt":"2013-08-06T13:59:45Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The -L/RE/ option of blame/log searches from the end of the previous -L\nrange, if any. Add new notation -L^/RE/ to override this behavior and\nsearch from start of file.\n\nThe new ^/RE/ syntax is valid only as the <start> argument of\n-L<start>,<end>. The <end> argument, as usual, is relative to <start>.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/line-range-format.txt |  1 +\n line-range.c                        | 10 ++++++++--\n t/annotate-tests.sh                 | 21 +++++++++++++++++++++\n 3 files changed, 30 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt\nindex 42d74f7..cf84417 100644\n--- a/Documentation/line-range-format.txt\n+++ b/Documentation/line-range-format.txt\n@@ -11,6 +11,7 @@ absolute line number (lines count from 1).\n This form will use the first line matching the given\n POSIX regex. If <start> is a regex, it will search from the end of\n the previous `-L` range, if any, otherwise from the start of file.\n+If <start> is ``^/regex/'', it will search from the start of file.\n If <end> is a regex, it will search\n starting at the line given by <start>.\n +\ndiff --git a/line-range.c b/line-range.c\nindex bbf3c0f..7048489 100644\n--- a/line-range.c\n+++ b/line-range.c\n@@ -59,8 +59,14 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,\n \t\treturn term;\n \t}\n \n-\tif (begin < 0)\n-\t\tbegin = -begin;\n+\tif (begin < 0) {\n+\t\tif (spec[0] != '^')\n+\t\t\tbegin = -begin;\n+\t\telse {\n+\t\t\tbegin = 1;\n+\t\t\tspec++;\n+\t\t}\n+\t}\n \n \tif (spec[0] != '/')\n \t\treturn spec;\ndiff --git a/t/annotate-tests.sh b/t/annotate-tests.sh\nindex b963d36..5a7d7c7 100644\n--- a/t/annotate-tests.sh\n+++ b/t/annotate-tests.sh\n@@ -323,6 +323,23 @@ test_expect_success 'blame -L /RE/ (relative: end-of-file)' '\n \ttest_must_fail $PROG -L, -L/$/ file\n '\n \n+test_expect_success 'blame -L ^/RE/ (absolute)' '\n+\tcheck_count -L3,3 -L^/dog/,+2 A 1 B2 1\n+'\n+\n+test_expect_success 'blame -L ^/RE/ (absolute: no preceding range)' '\n+\tcheck_count -L^/dog/,+2 A 1 B2 1\n+'\n+\n+test_expect_success 'blame -L ^/RE/ (absolute: not found)' '\n+\ttest_must_fail $PROG -L4,4 -L^/tambourine/ file\n+'\n+\n+test_expect_success 'blame -L ^/RE/ (absolute: end-of-file)' '\n+\tn=$(expr $(wc -l <file) + 1) &&\n+\tcheck_count -L$n -L^/$/,+2 A 1 C 1 E 1\n+'\n+\n test_expect_success 'setup -L :regex' '\n \ttr Q \"\\\\t\" >hello.c <<-\\EOF &&\n \tint main(int argc, const char *argv[])\n@@ -464,3 +481,7 @@ test_expect_success 'blame -L X,+N (non-numeric N)' '\n test_expect_success 'blame -L X,-N (non-numeric N)' '\n \ttest_must_fail $PROG -L1,-N file\n '\n+\n+test_expect_success 'blame -L ,^/RE/' '\n+\ttest_must_fail $PROG -L1,^/99/ file\n+'\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224675","messageId":"1375797589-65308-14-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 13/16] line-range: teach -L:RE to search from end of previous -L range","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:46Z","receivedAt":"2013-08-06T13:59:46Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"For consistency with -L/RE/, teach -L:RE to search relative to the end\nof the previous -L range, if any.\n\nThe new behavior invalidates one test in t4211 which assumes that -L:RE\nbegins searching at start of file. This test will be resurrected in a\nfollow-up patch which teaches -L:RE how to override the default relative\nsearch behavior.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/line-range-format.txt |  3 ++-\n line-range.c                        | 12 +++++++-----\n t/annotate-tests.sh                 | 16 ++++++++++++++++\n t/t4211-line-log.sh                 |  1 -\n 4 files changed, 25 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt\nindex cf84417..469d80b 100644\n--- a/Documentation/line-range-format.txt\n+++ b/Documentation/line-range-format.txt\n@@ -24,4 +24,5 @@ of lines before or after the line given by <start>.\n +\n If ``:<regex>'' is given in place of <start> and <end>, it denotes the range\n from the first funcname line that matches <regex>, up to the next\n-funcname line.\n+funcname line. ``:<regex>'' searches from the end of the previous `-L` range,\n+if any, otherwise from the start of file.\ndiff --git a/line-range.c b/line-range.c\nindex 7048489..4bae4bf 100644\n--- a/line-range.c\n+++ b/line-range.c\n@@ -161,7 +161,7 @@ static const char *find_funcname_matching_regexp(xdemitconf_t *xecfg, const char\n }\n \n static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_cb,\n-\t\t\t\t\tvoid *cb_data, long lines, long *begin, long *end,\n+\t\t\t\t\tvoid *cb_data, long lines, long anchor, long *begin, long *end,\n \t\t\t\t\tconst char *path)\n {\n \tchar *pattern;\n@@ -187,7 +187,8 @@ static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_\n \n \tpattern = xstrndup(arg+1, term-(arg+1));\n \n-\tstart = nth_line_cb(cb_data, 0);\n+\tanchor--; /* input is in human terms */\n+\tstart = nth_line_cb(cb_data, anchor);\n \n \tdrv = userdiff_find_by_path(path);\n \tif (drv && drv->funcname.pattern) {\n@@ -205,7 +206,8 @@ static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_\n \n \tp = find_funcname_matching_regexp(xecfg, (char*) start, &regexp);\n \tif (!p)\n-\t\tdie(\"-L parameter '%s': no match\", pattern);\n+\t\tdie(\"-L parameter '%s' starting at line %ld: no match\",\n+\t\t    pattern, anchor + 1);\n \t*begin = 0;\n \twhile (p > nth_line_cb(cb_data, *begin))\n \t\t(*begin)++;\n@@ -244,7 +246,7 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,\n \t\tanchor = lines + 1;\n \n \tif (*arg == ':') {\n-\t\targ = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path);\n+\t\targ = parse_range_funcname(arg, nth_line_cb, cb_data, lines, anchor, begin, end, path);\n \t\tif (!arg || *arg)\n \t\t\treturn -1;\n \t\treturn 0;\n@@ -269,7 +271,7 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,\n const char *skip_range_arg(const char *arg)\n {\n \tif (*arg == ':')\n-\t\treturn parse_range_funcname(arg, NULL, NULL, 0, NULL, NULL, NULL);\n+\t\treturn parse_range_funcname(arg, NULL, NULL, 0, 0, NULL, NULL, NULL);\n \n \targ = parse_loc(arg, NULL, NULL, 0, -1, NULL);\n \ndiff --git a/t/annotate-tests.sh b/t/annotate-tests.sh\nindex 5a7d7c7..4f7d6ba 100644\n--- a/t/annotate-tests.sh\n+++ b/t/annotate-tests.sh\n@@ -382,6 +382,22 @@ test_expect_success 'blame -L :nomatch' '\n \ttest_must_fail $PROG -L:nomatch hello.c\n '\n \n+test_expect_success 'blame -L :RE (relative)' '\n+\tcheck_count -f hello.c -L3,3 -L:ma.. F 1 H 4\n+'\n+\n+test_expect_success 'blame -L :RE (relative: no preceding range)' '\n+\tcheck_count -f hello.c -L:ma.. F 4 G 1\n+'\n+\n+test_expect_success 'blame -L :RE (relative: not found)' '\n+\ttest_must_fail $PROG -L3,3 -L:tambourine hello.c\n+'\n+\n+test_expect_success 'blame -L :RE (relative: end-of-file)' '\n+\ttest_must_fail $PROG -L, -L:main hello.c\n+'\n+\n test_expect_success 'setup incremental' '\n \t(\n \tGIT_AUTHOR_NAME=I &&\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex b01b3dd..8ba2d51 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -48,7 +48,6 @@ canned_test \"-M -L '/long f/,/^}/:b.c' move-support\" move-support-f\n canned_test \"-M -L ':f:b.c' parallel-change\" parallel-change-f-to-main\n \n canned_test \"-L 4,12:a.c -L :main:a.c simple\" multiple\n-canned_test \"-L 4,18:a.c -L :main:a.c simple\" multiple-overlapping\n canned_test \"-L :main:a.c -L 4,18:a.c simple\" multiple-overlapping\n canned_test \"-L 4:a.c -L 8,12:a.c simple\" multiple-superset\n canned_test \"-L 8,12:a.c -L 4:a.c simple\" multiple-superset\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224674","messageId":"1375797589-65308-15-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 14/16] line-range: teach -L^:RE to search from start of file","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:47Z","receivedAt":"2013-08-06T13:59:47Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The -L:RE option of blame/log searches from the end of the previous -L\nrange, if any. Add new notation -L^:RE to override this behavior and\nsearch from start of file.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/line-range-format.txt |  1 +\n line-range.c                        |  9 +++++++--\n t/annotate-tests.sh                 | 17 +++++++++++++++++\n t/t4211-line-log.sh                 |  1 +\n 4 files changed, 26 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt\nindex 469d80b..d7f2603 100644\n--- a/Documentation/line-range-format.txt\n+++ b/Documentation/line-range-format.txt\n@@ -26,3 +26,4 @@ If ``:<regex>'' is given in place of <start> and <end>, it denotes the range\n from the first funcname line that matches <regex>, up to the next\n funcname line. ``:<regex>'' searches from the end of the previous `-L` range,\n if any, otherwise from the start of file.\n+``^:<regex>'' searches from the start of file.\ndiff --git a/line-range.c b/line-range.c\nindex 4bae4bf..ede0c6c 100644\n--- a/line-range.c\n+++ b/line-range.c\n@@ -173,6 +173,11 @@ static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_\n \tint reg_error;\n \tregex_t regexp;\n \n+\tif (*arg == '^') {\n+\t\tanchor = 1;\n+\t\targ++;\n+\t}\n+\n \tassert(*arg == ':');\n \tterm = arg+1;\n \twhile (*term && *term != ':') {\n@@ -245,7 +250,7 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,\n \tif (anchor > lines)\n \t\tanchor = lines + 1;\n \n-\tif (*arg == ':') {\n+\tif (*arg == ':' || (*arg == '^' && *(arg + 1) == ':')) {\n \t\targ = parse_range_funcname(arg, nth_line_cb, cb_data, lines, anchor, begin, end, path);\n \t\tif (!arg || *arg)\n \t\t\treturn -1;\n@@ -270,7 +275,7 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,\n \n const char *skip_range_arg(const char *arg)\n {\n-\tif (*arg == ':')\n+\tif (*arg == ':' || (*arg == '^' && *(arg + 1) == ':'))\n \t\treturn parse_range_funcname(arg, NULL, NULL, 0, 0, NULL, NULL, NULL);\n \n \targ = parse_loc(arg, NULL, NULL, 0, -1, NULL);\ndiff --git a/t/annotate-tests.sh b/t/annotate-tests.sh\nindex 4f7d6ba..dabe89d 100644\n--- a/t/annotate-tests.sh\n+++ b/t/annotate-tests.sh\n@@ -398,6 +398,23 @@ test_expect_success 'blame -L :RE (relative: end-of-file)' '\n \ttest_must_fail $PROG -L, -L:main hello.c\n '\n \n+test_expect_success 'blame -L ^:RE (absolute)' '\n+\tcheck_count -f hello.c -L3,3 -L^:ma.. F 4 G 1\n+'\n+\n+test_expect_success 'blame -L ^:RE (absolute: no preceding range)' '\n+\tcheck_count -f hello.c -L^:ma.. F 4 G 1\n+'\n+\n+test_expect_success 'blame -L ^:RE (absolute: not found)' '\n+\ttest_must_fail $PROG -L4,4 -L^:tambourine hello.c\n+'\n+\n+test_expect_success 'blame -L ^:RE (absolute: end-of-file)' '\n+\tn=$(printf \"%d\" $(wc -l <hello.c)) &&\n+\tcheck_count -f hello.c -L$n -L^:ma.. F 4 G 1 H 1\n+'\n+\n test_expect_success 'setup incremental' '\n \t(\n \tGIT_AUTHOR_NAME=I &&\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex 8ba2d51..7369d3c 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -48,6 +48,7 @@ canned_test \"-M -L '/long f/,/^}/:b.c' move-support\" move-support-f\n canned_test \"-M -L ':f:b.c' parallel-change\" parallel-change-f-to-main\n \n canned_test \"-L 4,12:a.c -L :main:a.c simple\" multiple\n+canned_test \"-L 4,18:a.c -L ^:main:a.c simple\" multiple-overlapping\n canned_test \"-L :main:a.c -L 4,18:a.c simple\" multiple-overlapping\n canned_test \"-L 4:a.c -L 8,12:a.c simple\" multiple-superset\n canned_test \"-L 8,12:a.c -L 4:a.c simple\" multiple-superset\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224678","messageId":"1375797589-65308-16-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 15/16] t8001/t8002: blame: add tests of -L line numbers less than 1","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:48Z","receivedAt":"2013-08-06T13:59:48Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"git-blame -L is documented as accepting 1-based line numbers. When\nhanded a line number less than 1, -L's behavior is undocumented and\nundefined; it's also nonsensical and should be rejected but is\nnevertheless accepted. Demonstrate this shortcoming.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/annotate-tests.sh | 12 ++++++++++++\n 1 file changed, 12 insertions(+)\n\ndiff --git a/t/annotate-tests.sh b/t/annotate-tests.sh\nindex dabe89d..376b042 100644\n--- a/t/annotate-tests.sh\n+++ b/t/annotate-tests.sh\n@@ -185,6 +185,18 @@ test_expect_success 'blame -L Y,X (undocumented)' '\n \tcheck_count -L6,3 B 1 B1 1 B2 1 D 1\n '\n \n+test_expect_failure 'blame -L -X' '\n+\ttest_must_fail $PROG -L-1 file\n+'\n+\n+test_expect_failure 'blame -L 0' '\n+\ttest_must_fail $PROG -L0 file\n+'\n+\n+test_expect_failure 'blame -L ,0' '\n+\ttest_must_fail $PROG -L,0 file\n+'\n+\n test_expect_success 'blame -L ,+0' '\n \ttest_must_fail $PROG -L,+0 file\n '\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224676","messageId":"1375797589-65308-17-git-send-email-sunshine@sunshineco.com","threadId":"34624","inReplyTo":"1375797589-65308-1-git-send-email-sunshine@sunshineco.com","subject":"[PATCH v2 16/16] line-range: reject -L line numbers less than 1","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T13:59:49Z","receivedAt":"2013-08-06T13:59:49Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Since inception, git-blame -L has been documented as accepting 1-based\nline numbers. When handed a line number less than 1, -L's behavior is\nundocumented and undefined; it's also nonsensical and should be\ndiagnosed as an error. Do so.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n line-range.c        |  5 ++++-\n t/annotate-tests.sh | 18 +++++++++---------\n 2 files changed, 13 insertions(+), 10 deletions(-)\n\ndiff --git a/line-range.c b/line-range.c\nindex ede0c6c..de4e32f 100644\n--- a/line-range.c\n+++ b/line-range.c\n@@ -54,8 +54,11 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,\n \t}\n \tnum = strtol(spec, &term, 10);\n \tif (term != spec) {\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\tif (num <= 0)\n+\t\t\t\tdie(\"-L invalid line number: %ld\", num);\n \t\t\t*ret = num;\n+\t\t}\n \t\treturn term;\n \t}\n \ndiff --git a/t/annotate-tests.sh b/t/annotate-tests.sh\nindex 376b042..99caa42 100644\n--- a/t/annotate-tests.sh\n+++ b/t/annotate-tests.sh\n@@ -185,15 +185,15 @@ test_expect_success 'blame -L Y,X (undocumented)' '\n \tcheck_count -L6,3 B 1 B1 1 B2 1 D 1\n '\n \n-test_expect_failure 'blame -L -X' '\n+test_expect_success 'blame -L -X' '\n \ttest_must_fail $PROG -L-1 file\n '\n \n-test_expect_failure 'blame -L 0' '\n+test_expect_success 'blame -L 0' '\n \ttest_must_fail $PROG -L0 file\n '\n \n-test_expect_failure 'blame -L ,0' '\n+test_expect_success 'blame -L ,0' '\n \ttest_must_fail $PROG -L,0 file\n '\n \n@@ -447,8 +447,8 @@ test_expect_success 'blame empty' '\n \tcheck_count -h HEAD^^ -f incremental\n '\n \n-test_expect_success 'blame -L 0 empty (undocumented)' '\n-\tcheck_count -h HEAD^^ -f incremental -L0\n+test_expect_success 'blame -L 0 empty' '\n+\ttest_must_fail $PROG -L0 incremental HEAD^^\n '\n \n test_expect_success 'blame -L 1 empty' '\n@@ -463,8 +463,8 @@ test_expect_success 'blame half' '\n \tcheck_count -h HEAD^ -f incremental I 1\n '\n \n-test_expect_success 'blame -L 0 half (undocumented)' '\n-\tcheck_count -h HEAD^ -f incremental -L0 I 1\n+test_expect_success 'blame -L 0 half' '\n+\ttest_must_fail $PROG -L0 incremental HEAD^\n '\n \n test_expect_success 'blame -L 1 half' '\n@@ -483,8 +483,8 @@ test_expect_success 'blame full' '\n \tcheck_count -f incremental I 1\n '\n \n-test_expect_success 'blame -L 0 full (undocumented)' '\n-\tcheck_count -f incremental -L0 I 1\n+test_expect_success 'blame -L 0 full' '\n+\ttest_must_fail $PROG -L0 incremental\n '\n \n test_expect_success 'blame -L 1 full' '\n-- \n1.8.4.rc1.409.gbd48715\n"},{"id":"224712","messageId":"7v1u66fqrz.fsf@alter.siamese.dyndns.org","threadId":"34624","inReplyTo":"1375797589-65308-6-git-send-email-sunshine@sunshineco.com","subject":"Re: [PATCH v2 05/16] blame: accept multiple -L ranges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-06T21:33:36Z","receivedAt":"2013-08-06T21:33:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"> +\tfor (range_i = ranges.nr; range_i > 0; --range_i) {\n> +\t\tconst struct range *r = &ranges.ranges[range_i - 1];\n> +\t\tlong bottom = r->start;\n> +\t\tlong top = r->end;\n> +\t\tstruct blame_entry *next = ent;\n> +\t\tent = xcalloc(1, sizeof(*ent));\n> +\t\tent->lno = bottom;\n> +\t\tent->num_lines = top - bottom;\n> +\t\tent->suspect = o;\n> +\t\tent->s_lno = bottom;\n> +\t\tent->next = next;\n> +\t\tif (next)\n> +\t\t\tnext->prev = ent;\n> +\t\torigin_incref(o);\n> +\t}\n> +\torigin_decref(o);\n\nHmph, I do not see where the need for this decref is coming from.\nDid we incref once too many somewhere?\n\n> +\trange_set_release(&ranges);\n> +\tstring_list_clear(&range_list, 0);\n>  \n>  \tsb.ent = ent;\n>  \tsb.path = path;\n"},{"id":"224715","messageId":"7vwqnyebpv.fsf@alter.siamese.dyndns.org","threadId":"34624","inReplyTo":"1375797589-65308-9-git-send-email-sunshine@sunshineco.com","subject":"Re: [PATCH v2 08/16] line-range: teach -L/RE/ to search relative to anchor point","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-06T21:44:12Z","receivedAt":"2013-08-06T21:44:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> Range specification -L/RE/ for blame/log unconditionally begins\n> searching at line one. Mailing list discussion [1] suggests that, in the\n> presence of multiple -L options, -L/RE/ should search relative to the\n> endpoint of the previous -L range, if any.\n>\n> Teach the parsing machinery underlying blame's and log's -L options to\n> accept a start point for -L/RE/ searches. Follow-up patches will upgrade\n> blame and log to take advantage of this ability.\n>\n> [1]: http://thread.gmane.org/gmane.comp.version-control.git/229755/focus=229966\n>\n> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n> ---\n>  builtin/blame.c |  2 +-\n>  line-log.c      |  2 +-\n>  line-range.c    | 30 ++++++++++++++++++++++++++----\n>  line-range.h    |  5 ++++-\n>  4 files changed, 32 insertions(+), 7 deletions(-)\n>\n> diff --git a/builtin/blame.c b/builtin/blame.c\n> index 2f4d9e2..7b084d8 100644\n> --- a/builtin/blame.c\n> +++ b/builtin/blame.c\n> @@ -2479,7 +2479,7 @@ parse_done:\n>  \tfor (range_i = 0; range_i < range_list.nr; ++range_i) {\n>  \t\tlong bottom, top;\n>  \t\tif (parse_range_arg(range_list.items[range_i].string,\n> -\t\t\t\t    nth_line_cb, &sb, lno,\n> +\t\t\t\t    nth_line_cb, &sb, lno, 1,\n>  \t\t\t\t    &bottom, &top, sb.path))\n>  \t\t\tusage(blame_usage);\n>  \t\tif (lno < top || ((lno || bottom) && lno < bottom))\n> diff --git a/line-log.c b/line-log.c\n> index bdadf35..38f827b 100644\n> --- a/line-log.c\n> +++ b/line-log.c\n> @@ -591,7 +591,7 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)\n>  \t\tcb_data.line_ends = ends;\n>  \n>  \t\tif (parse_range_arg(range_part, nth_line, &cb_data,\n> -\t\t\t\t    lines, &begin, &end,\n> +\t\t\t\t    lines, 1, &begin, &end,\n\nThis one feeds \"1\" to anchor, which in turn is given to parse_loc as\n\"-1\" and then (after bypassing the <end> part support) its sign\nflipped once again to become \"begin=1\" in parse_loc().  Then we run\nregexp search starting from (1-based) 1st line, retaining the\n\"always scan from the beginning\" behaviour in this step in the\nseries.\n\nOK, looks sensible.\n"},{"id":"224713","messageId":"7vsiymebo4.fsf@alter.siamese.dyndns.org","threadId":"34624","inReplyTo":"1375797589-65308-10-git-send-email-sunshine@sunshineco.com","subject":"Re: [PATCH v2 09/16] blame: teach -L/RE/ to search from end of previous -L range","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-06T21:45:15Z","receivedAt":"2013-08-06T21:45:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n> ---\n\nWith the previous step, what this one does is fairly obvious and\nstraight-forward.  Looking good ;-)\n\n>  builtin/blame.c     |  5 ++++-\n>  t/annotate-tests.sh | 20 ++++++++++++++++++++\n>  2 files changed, 24 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/blame.c b/builtin/blame.c\n> index 7b084d8..1bf8056 100644\n> --- a/builtin/blame.c\n> +++ b/builtin/blame.c\n> @@ -2280,6 +2280,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n>  \tint cmd_is_annotate = !strcmp(argv[0], \"annotate\");\n>  \tstruct range_set ranges;\n>  \tunsigned int range_i;\n> +\tlong anchor;\n>  \n>  \tgit_config(git_blame_config, NULL);\n>  \tinit_revisions(&revs, NULL);\n> @@ -2475,11 +2476,12 @@ parse_done:\n>  \tif (lno && !range_list.nr)\n>  \t\tstring_list_append(&range_list, xstrdup(\"1\"));\n>  \n> +\tanchor = 1;\n>  \trange_set_init(&ranges, range_list.nr);\n>  \tfor (range_i = 0; range_i < range_list.nr; ++range_i) {\n>  \t\tlong bottom, top;\n>  \t\tif (parse_range_arg(range_list.items[range_i].string,\n> -\t\t\t\t    nth_line_cb, &sb, lno, 1,\n> +\t\t\t\t    nth_line_cb, &sb, lno, anchor,\n>  \t\t\t\t    &bottom, &top, sb.path))\n>  \t\t\tusage(blame_usage);\n>  \t\tif (lno < top || ((lno || bottom) && lno < bottom))\n> @@ -2490,6 +2492,7 @@ parse_done:\n>  \t\t\ttop = lno;\n>  \t\tbottom--;\n>  \t\trange_set_append_unsafe(&ranges, bottom, top);\n> +\t\tanchor = top + 1;\n>  \t}\n>  \tsort_and_merge_range_set(&ranges);\n>  \n> diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh\n> index 77083d9..b963d36 100644\n> --- a/t/annotate-tests.sh\n> +++ b/t/annotate-tests.sh\n> @@ -303,6 +303,26 @@ test_expect_success 'blame -L multiple (superset/subset: unordered)' '\n>  \tcheck_count -L3,5 -L2,8 A 1 B 1 B1 1 B2 1 C 1 D 1 \"A U Thor\" 1\n>  '\n>  \n> +test_expect_success 'blame -L /RE/ (relative)' '\n> +\tcheck_count -L3,3 -L/fox/ B1 1 B2 1 C 1 D 1 \"A U Thor\" 1\n> +'\n> +\n> +test_expect_success 'blame -L /RE/ (relative: no preceding range)' '\n> +\tcheck_count -L/dog/ A 1 B 1 B1 1 B2 1 C 1 D 1 \"A U Thor\" 1\n> +'\n> +\n> +test_expect_success 'blame -L /RE/ (relative: adjacent)' '\n> +\tcheck_count -L1,1 -L/dog/,+1 A 1 E 1\n> +'\n> +\n> +test_expect_success 'blame -L /RE/ (relative: not found)' '\n> +\ttest_must_fail $PROG -L4,4 -L/dog/ file\n> +'\n> +\n> +test_expect_success 'blame -L /RE/ (relative: end-of-file)' '\n> +\ttest_must_fail $PROG -L, -L/$/ file\n> +'\n> +\n>  test_expect_success 'setup -L :regex' '\n>  \ttr Q \"\\\\t\" >hello.c <<-\\EOF &&\n>  \tint main(int argc, const char *argv[])\n"},{"id":"224717","messageId":"CAPig+cTHbV-ijfXNBn45v2opOH7Jh0A22QaxCeKhRBPA39xAwQ@mail.gmail.com","threadId":"34624","inReplyTo":"7v1u66fqrz.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v2 05/16] blame: accept multiple -L ranges","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2013-08-06T22:44:44Z","receivedAt":"2013-08-06T22:44:44Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Aug 6, 2013 at 5:33 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> +     for (range_i = ranges.nr; range_i > 0; --range_i) {\n>> +             const struct range *r = &ranges.ranges[range_i - 1];\n>> +             long bottom = r->start;\n>> +             long top = r->end;\n>> +             struct blame_entry *next = ent;\n>> +             ent = xcalloc(1, sizeof(*ent));\n>> +             ent->lno = bottom;\n>> +             ent->num_lines = top - bottom;\n>> +             ent->suspect = o;\n>> +             ent->s_lno = bottom;\n>> +             ent->next = next;\n>> +             if (next)\n>> +                     next->prev = ent;\n>> +             origin_incref(o);\n>> +     }\n>> +     origin_decref(o);\n>\n> Hmph, I do not see where the need for this decref is coming from.\n> Did we incref once too many somewhere?\n\nEach constructed blame_entry must own a reference to the suspect.\no->refcnt should equal the number of blame_entries. At construction, a\n'struct origin' has refcnt 1. In the original code, which supported\nonly a single initial range (blame_entry), we had:\n\n  o = get-initial-suspect();  # refcnt already 1\n  ent->suspect = o;  # refcnt still 1\n  sb.ent = ent;\n  assign_blame(&sb);\n\nSo, o->refcnt equals the number of blame_entries (1) when\nassign_blame() is called.\n\nThe new for-loop calls origin_incref() on each iteration since each\nblame_entry needs to own a reference to the suspect. Assume that we\nhave two disjoint -L ranges:\n\n  o = get-initial-suspect();  # refcnt already 1\n  foreach range:\n    ent = new blame_entry;\n    ent->suspect = o;\n    origin_incref(o);  # refcnt++\n  end\n  # for 2 ranges, refcnt incremented twice, so value is 3\n  origin_decref(o);  # refcnt = 2\n  sb.ent = ent;\n  assign_blame(&sb);\n\nThus, as with the original code, o->refcnt equals the number of\nblame_entries (2) when assign_blame() is called.\n\nThe same holds for the boundary case when the file is empty and there\nis no range. o->refcnt starts at 1, the loop is never entered so no\nblame_entries are created, and o->refcnt gets decremented to 0, which\nagain matches the number of blame_entries (0).\n"},{"id":"224718","messageId":"7vk3jye8rz.fsf@alter.siamese.dyndns.org","threadId":"34624","inReplyTo":"CAPig+cTHbV-ijfXNBn45v2opOH7Jh0A22QaxCeKhRBPA39xAwQ@mail.gmail.com","subject":"Re: [PATCH v2 05/16] blame: accept multiple -L ranges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-08-06T22:47:44Z","receivedAt":"2013-08-06T22:47:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> Each constructed blame_entry must own a reference to the suspect.\n> o->refcnt should equal the number of blame_entries. At construction, a\n> 'struct origin' has refcnt 1. In the original code, which supported\n> only a single initial range (blame_entry), we had:\n>\n>   o = get-initial-suspect();  # refcnt already 1\n>   ent->suspect = o;  # refcnt still 1\n\nAh, of course.\n\nI forgot that I initialized a new origin with refcnt 1 exactly for\nthis.  As you use it once for each range, you would need to\ncompensate for it.\n\nThanks.\n"}]}