log -L: :pattern:file syntax to find by funcname
authorThomas Rast <trast@student.ethz.ch>
Thu, 28 Mar 2013 16:47:33 +0000 (17:47 +0100)
committerJunio C Hamano <gitster@pobox.com>
Thu, 28 Mar 2013 17:30:04 +0000 (10:30 -0700)
This new syntax finds a funcname matching /pattern/, and then takes from there
up to (but not including) the next funcname. So you can say

git log -L:main:main.c

and it will dig up the main() function and show its line-log, provided
there are no other funcnames matching 'main'.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/blame-options.txt
Documentation/git-blame.txt
Documentation/git-log.txt
Documentation/line-range-format.txt
builtin/blame.c
line-log.c
line-range.c
line-range.h
t/t4211-line-log.sh
t/t4211/expect.simple-f-to-main [new file with mode: 0644]
t/t4211/expect.simple-main-to-end [new file with mode: 0644]
index 6998d9f5bf73330ca8a830a31e1845ee97ac5497..e9f984ba01597234abee94fffd4f71beb2fbbc02 100644 (file)
@@ -9,7 +9,7 @@
 --show-stats::
        Include additional statistics at the end of blame output.
 
--L <start>,<end>::
+-L <start>,<end>, -L :<regex>::
        Annotate only the given line range.  <start> and <end> can take
        one of these forms:
 
index 9a05c2b3d24ca63163d76476d710e17f31617ff4..6cea7f1ce1ba3a917ddd0cc9a12195d6f768bf76 100644 (file)
@@ -8,9 +8,9 @@ git-blame - Show what revision and author last modified each line of a file
 SYNOPSIS
 --------
 [verse]
-'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L n,m]
-           [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--abbrev=<n>]
-           [<rev> | --contents <file> | --reverse <rev>] [--] <file>
+'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>]
+           [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file>
 
 DESCRIPTION
 -----------
index 8727c60ab36365c88040c17e7e487013bc5ac461..4850226aaf16e14731e5571db95645d502fdfe2d 100644 (file)
@@ -69,12 +69,13 @@ 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 <start>,<end>:<file>, -L :<regex>:<file>::
+
        Trace the evolution of the line range given by "<start>,<end>"
-       within the <file>.  You may not give any pathspec limiters.
-       This is currently limited to a walk starting from a single
-       revision, i.e., you may only give zero or one positive
-       revision arguments.
+       (or the funcname regex <regex>) within the <file>.  You may
+       not give any pathspec limiters.  This is currently limited to
+       a walk starting from a single revision, i.e., you may only
+       give zero or one positive revision arguments.
 
 <start> and <end> can take one of these forms:
 
index 265bc23290965d365b477791fc22ba32c94f71e0..3e7ce72daab23d6dfcf12d1e0dd1dd6be705e56d 100644 (file)
@@ -16,3 +16,10 @@ starting at the line given by <start>.
 This is only valid for <end> and will specify a number
 of lines before or after the line given by <start>.
 +
+
+- :regex
++
+If the option's argument is of the form :regex, it denotes the range
+from the first funcname line that matches <regex>, up to the next
+funcname line.
++
index 20eb439cb875188cdad92624782366d81c6a5dff..1c09d552fa00f6180cd2a49bf713c7e3299c1d28 100644 (file)
@@ -1940,7 +1940,7 @@ static void prepare_blame_range(struct scoreboard *sb,
                                long lno,
                                long *bottom, long *top)
 {
-       if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top))
+       if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))
                usage(blame_usage);
 }
 
index 624423142ef99e4ad0a008b738c5978bb9521467..68972e24c7826663f42946a54efc0a3fa5505b4d 100644 (file)
@@ -12,6 +12,7 @@
 #include "strbuf.h"
 #include "log-tree.h"
 #include "graph.h"
+#include "userdiff.h"
 #include "line-log.h"
 
 static void range_set_grow(struct range_set *rs, size_t extra)
@@ -438,7 +439,6 @@ static void range_set_map_across_diff(struct range_set *out,
        *touched_out = touched;
 }
 
-
 static struct commit *check_single_commit(struct rev_info *revs)
 {
        struct object *commit = NULL;
@@ -559,7 +559,8 @@ 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, &begin, &end,
+                                   spec->path))
                        die("malformed -L argument '%s'", range_part);
                if (begin < 1)
                        begin = 1;
index 7a7ca3c2bde9c7bf6b1ae29537b42389c11bd8e9..8faf94374517b605d2529de2f4bafc869b72df60 100644 (file)
@@ -1,5 +1,8 @@
 #include "git-compat-util.h"
 #include "line-range.h"
+#include "xdiff-interface.h"
+#include "strbuf.h"
+#include "userdiff.h"
 
 /*
  * Parse one item in the -L option
@@ -84,9 +87,137 @@ static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
        }
 }
 
+static int match_funcname(xdemitconf_t *xecfg, const char *bol, const char *eol)
+{
+       if (xecfg) {
+               char buf[1];
+               return xecfg->find_func(bol, eol - bol, buf, 1,
+                                       xecfg->find_func_priv) >= 0;
+       }
+
+       if (bol == eol)
+               return 0;
+       if (isalpha(*bol) || *bol == '_' || *bol == '$')
+               return 1;
+       return 0;
+}
+
+static const char *find_funcname_matching_regexp(xdemitconf_t *xecfg, const char *start,
+                                                regex_t *regexp)
+{
+       int reg_error;
+       regmatch_t match[1];
+       while (1) {
+               const char *bol, *eol;
+               reg_error = regexec(regexp, start, 1, match, 0);
+               if (reg_error == REG_NOMATCH)
+                       return NULL;
+               else if (reg_error) {
+                       char errbuf[1024];
+                       regerror(reg_error, regexp, errbuf, 1024);
+                       die("-L parameter: regexec() failed: %s", errbuf);
+               }
+               /* determine extent of line matched */
+               bol = start+match[0].rm_so;
+               eol = start+match[0].rm_eo;
+               while (bol > start && *bol != '\n')
+                       bol--;
+               if (*bol == '\n')
+                       bol++;
+               while (*eol && *eol != '\n')
+                       eol++;
+               if (*eol == '\n')
+                       eol++;
+               /* is it a funcname line? */
+               if (match_funcname(xecfg, (char*) bol, (char*) eol))
+                       return bol;
+               start = eol;
+       }
+}
+
+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,
+                                       const char *path)
+{
+       char *pattern;
+       const char *term;
+       struct userdiff_driver *drv;
+       xdemitconf_t *xecfg = NULL;
+       const char *start;
+       const char *p;
+       int reg_error;
+       regex_t regexp;
+
+       assert(*arg == ':');
+       term = arg+1;
+       while (*term && *term != ':') {
+               if (*term == '\\' && *(term+1))
+                       term++;
+               term++;
+       }
+       if (term == arg+1)
+               return NULL;
+       if (!begin) /* skip_range_arg case */
+               return term;
+
+       pattern = xstrndup(arg+1, term-(arg+1));
+
+       start = nth_line_cb(cb_data, 0);
+
+       drv = userdiff_find_by_path(path);
+       if (drv && drv->funcname.pattern) {
+               const struct userdiff_funcname *pe = &drv->funcname;
+               xecfg = xcalloc(1, sizeof(*xecfg));
+               xdiff_set_find_func(xecfg, pe->pattern, pe->cflags);
+       }
+
+       reg_error = regcomp(&regexp, pattern, REG_NEWLINE);
+       if (reg_error) {
+               char errbuf[1024];
+               regerror(reg_error, &regexp, errbuf, 1024);
+               die("-L parameter '%s': %s", pattern, errbuf);
+       }
+
+       p = find_funcname_matching_regexp(xecfg, (char*) start, &regexp);
+       if (!p)
+               die("-L parameter '%s': no match", pattern);
+       *begin = 0;
+       while (p > nth_line_cb(cb_data, *begin))
+               (*begin)++;
+
+       if (*begin >= lines)
+               die("-L parameter '%s' matches at EOF", pattern);
+
+       *end = *begin+1;
+       while (*end < lines) {
+               const char *bol = nth_line_cb(cb_data, *end);
+               const char *eol = nth_line_cb(cb_data, *end+1);
+               if (match_funcname(xecfg, bol, eol))
+                       break;
+               (*end)++;
+       }
+
+       regfree(&regexp);
+       free(xecfg);
+       free(pattern);
+
+       /* compensate for 1-based numbering */
+       (*begin)++;
+
+       return term;
+}
+
 int parse_range_arg(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 *begin, long *end,
+                   const char *path)
 {
+       if (*arg == ':') {
+               arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path);
+               if (!arg || *arg)
+                       return -1;
+               return 0;
+       }
+
        arg = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin);
 
        if (*arg == ',')
@@ -100,6 +231,9 @@ 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);
+
        arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
 
        if (*arg == ',')
index 88aaf08b7f7f56f6ea7b1ed57490682e133874d6..ae3d0123b447c1c619695c2d0aaf619d1daaa991 100644 (file)
@@ -19,7 +19,8 @@ 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,
-                          long *begin, long *end);
+                          long *begin, long *end,
+                          const char *path);
 
 /*
  * Scan past a range argument that could be parsed by
index 9f36f17384ca751bc6708fc62dc363bd4fcd73e3..426a82868e13235a9fbdb0c48d5c727fb2fe9c91 100755 (executable)
@@ -25,8 +25,10 @@ test_bad_opts () {
 canned_test "-L 4,12:a.c simple" simple-f
 canned_test "-L 4,+9:a.c simple" simple-f
 canned_test "-L '/long f/,/^}/:a.c' simple" simple-f
+canned_test "-L :f:a.c simple" simple-f-to-main
 
 canned_test "-L '/main/,/^}/:a.c' simple" simple-main
+canned_test "-L :main:a.c simple" simple-main-to-end
 
 canned_test "-L 1,+4:a.c simple" beginning-of-file
 
@@ -45,5 +47,7 @@ test_bad_opts "-L 1:simple" "There is no path"
 test_bad_opts "-L '/foo:b.c'" "argument.*not of the form"
 test_bad_opts "-L 1000:b.c" "has only.*lines"
 test_bad_opts "-L 1,1000:b.c" "has only.*lines"
+test_bad_opts "-L :b.c" "argument.*not of the form"
+test_bad_opts "-L :foo:b.c" "no match"
 
 test_done
diff --git a/t/t4211/expect.simple-f-to-main b/t/t4211/expect.simple-f-to-main
new file mode 100644 (file)
index 0000000..a475768
--- /dev/null
@@ -0,0 +1,100 @@
+commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:41 2013 +0100
+
+    touch comment
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+ long f(long x)
+ {
+       int s = 0;
+       while (x) {
+               x >>= 1;
+               s++;
+       }
+       return s;
+ }
+ /*
+- * A comment.
++ * This is only an example!
+  */
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,14 +3,14 @@
+-int f(int x)
++long f(long x)
+ {
+       int s = 0;
+       while (x) {
+               x >>= 1;
+               s++;
+       }
+       return s;
+ }
+ /*
+  * A comment.
+  */
+
+commit f04fb20f2c77850996cba739709acc6faecc58f7
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:55 2013 +0100
+
+    change f()
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -3,13 +3,14 @@
+ int f(int x)
+ {
+       int s = 0;
+       while (x) {
+               x >>= 1;
+               s++;
+       }
++      return s;
+ }
+ /*
+  * A comment.
+  */
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +3,13 @@
++int f(int x)
++{
++      int s = 0;
++      while (x) {
++              x >>= 1;
++              s++;
++      }
++}
++
++/*
++ * A comment.
++ */
++
diff --git a/t/t4211/expect.simple-main-to-end b/t/t4211/expect.simple-main-to-end
new file mode 100644 (file)
index 0000000..8480bd9
--- /dev/null
@@ -0,0 +1,70 @@
+commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:43 2013 +0100
+
+    change back to complete line
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,7 @@
+ int main ()
+ {
+       printf("%ld\n", f(15));
+       return 0;
+-}
+\ No newline at end of file
++}
++
++/* incomplete lines are bad! */
+
+commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:48:10 2013 +0100
+
+    change to an incomplete line at end
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -18,5 +18,5 @@
+ int main ()
+ {
+       printf("%ld\n", f(15));
+       return 0;
+-}
++}
+\ No newline at end of file
+
+commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:45:16 2013 +0100
+
+    touch both functions
+
+diff --git a/a.c b/a.c
+--- a/a.c
++++ b/a.c
+@@ -17,5 +17,5 @@
+ int main ()
+ {
+-      printf("%d\n", f(15));
++      printf("%ld\n", f(15));
+       return 0;
+ }
+
+commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
+Author: Thomas Rast <trast@student.ethz.ch>
+Date:   Thu Feb 28 10:44:48 2013 +0100
+
+    initial
+
+diff --git a/a.c b/a.c
+--- /dev/null
++++ b/a.c
+@@ -0,0 +16,5 @@
++int main ()
++{
++      printf("%d\n", f(15));
++      return 0;
++}