log -L: :pattern:file syntax to find by funcname

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>

Thomas Rast committed Mar 28, 2013 at 17:47 UTC 13b8f68c1f0b6d4ff5a7bed5a834e7e5e34a9fd0
11 files changed +332 -14
Documentation/blame-options.txt
+1 -1
@@ -9,7 +9,7 @@
9 --show-stats::
10 Include additional statistics at the end of blame output.
11
12 --L <start>,<end>::
12 +-L <start>,<end>, -L :<regex>::
13 Annotate only the given line range. <start> and <end> can take
14 one of these forms:
15
Documentation/git-blame.txt
+3 -3
@@ -8,9 +8,9 @@ git-blame - Show what revision and author last modified each line of a file
8 SYNOPSIS
9 --------
10 [verse]
11 -'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L n,m]
12 - [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--abbrev=<n>]
13 - [<rev> | --contents <file> | --reverse <rev>] [--] <file>
11 +'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
12 + [-L n,m | -L :fn] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
13 + [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file>
14
15 DESCRIPTION
16 -----------
Documentation/git-log.txt
+6 -5
@@ -69,12 +69,13 @@ produced by --stat etc.
69 Note that only message is considered, if also a diff is shown
70 its size is not included.
71
72 --L <start>,<end>:<file>::
72 +-L <start>,<end>:<file>, -L :<regex>:<file>::
73 +
74 Trace the evolution of the line range given by "<start>,<end>"
74 - within the <file>. You may not give any pathspec limiters.
75 - This is currently limited to a walk starting from a single
76 - revision, i.e., you may only give zero or one positive
77 - revision arguments.
75 + (or the funcname regex <regex>) within the <file>. You may
76 + not give any pathspec limiters. This is currently limited to
77 + a walk starting from a single revision, i.e., you may only
78 + give zero or one positive revision arguments.
79
80 <start> and <end> can take one of these forms:
81
Documentation/line-range-format.txt
+7
@@ -16,3 +16,10 @@ starting at the line given by <start>.
16 This is only valid for <end> and will specify a number
17 of lines before or after the line given by <start>.
18 +
19 +
20 +- :regex
21 ++
22 +If the option's argument is of the form :regex, it denotes the range
23 +from the first funcname line that matches <regex>, up to the next
24 +funcname line.
25 ++
builtin/blame.c
+1 -1
@@ -1940,7 +1940,7 @@ static void prepare_blame_range(struct scoreboard *sb,
1940 long lno,
1941 long *bottom, long *top)
1942 {
1943 - if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top))
1943 + if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))
1944 usage(blame_usage);
1945 }
1946
line-log.c
+3 -2
@@ -12,6 +12,7 @@
12 #include "strbuf.h"
13 #include "log-tree.h"
14 #include "graph.h"
15 +#include "userdiff.h"
16 #include "line-log.h"
17
18 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,
439 *touched_out = touched;
440 }
441
441 -
442 static struct commit *check_single_commit(struct rev_info *revs)
443 {
444 struct object *commit = NULL;
@@ -559,7 +559,8 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
559 cb_data.line_ends = ends;
560
561 if (parse_range_arg(range_part, nth_line, &cb_data,
562 - lines, &begin, &end))
562 + lines, &begin, &end,
563 + spec->path))
564 die("malformed -L argument '%s'", range_part);
565 if (begin < 1)
566 begin = 1;
line-range.c
+135 -1
@@ -1,5 +1,8 @@
1 #include "git-compat-util.h"
2 #include "line-range.h"
3 +#include "xdiff-interface.h"
4 +#include "strbuf.h"
5 +#include "userdiff.h"
6
7 /*
8 * 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,
87 }
88 }
89
90 +static int match_funcname(xdemitconf_t *xecfg, const char *bol, const char *eol)
91 +{
92 + if (xecfg) {
93 + char buf[1];
94 + return xecfg->find_func(bol, eol - bol, buf, 1,
95 + xecfg->find_func_priv) >= 0;
96 + }
97 +
98 + if (bol == eol)
99 + return 0;
100 + if (isalpha(*bol) || *bol == '_' || *bol == '$')
101 + return 1;
102 + return 0;
103 +}
104 +
105 +static const char *find_funcname_matching_regexp(xdemitconf_t *xecfg, const char *start,
106 + regex_t *regexp)
107 +{
108 + int reg_error;
109 + regmatch_t match[1];
110 + while (1) {
111 + const char *bol, *eol;
112 + reg_error = regexec(regexp, start, 1, match, 0);
113 + if (reg_error == REG_NOMATCH)
114 + return NULL;
115 + else if (reg_error) {
116 + char errbuf[1024];
117 + regerror(reg_error, regexp, errbuf, 1024);
118 + die("-L parameter: regexec() failed: %s", errbuf);
119 + }
120 + /* determine extent of line matched */
121 + bol = start+match[0].rm_so;
122 + eol = start+match[0].rm_eo;
123 + while (bol > start && *bol != '\n')
124 + bol--;
125 + if (*bol == '\n')
126 + bol++;
127 + while (*eol && *eol != '\n')
128 + eol++;
129 + if (*eol == '\n')
130 + eol++;
131 + /* is it a funcname line? */
132 + if (match_funcname(xecfg, (char*) bol, (char*) eol))
133 + return bol;
134 + start = eol;
135 + }
136 +}
137 +
138 +static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_cb,
139 + void *cb_data, long lines, long *begin, long *end,
140 + const char *path)
141 +{
142 + char *pattern;
143 + const char *term;
144 + struct userdiff_driver *drv;
145 + xdemitconf_t *xecfg = NULL;
146 + const char *start;
147 + const char *p;
148 + int reg_error;
149 + regex_t regexp;
150 +
151 + assert(*arg == ':');
152 + term = arg+1;
153 + while (*term && *term != ':') {
154 + if (*term == '\\' && *(term+1))
155 + term++;
156 + term++;
157 + }
158 + if (term == arg+1)
159 + return NULL;
160 + if (!begin) /* skip_range_arg case */
161 + return term;
162 +
163 + pattern = xstrndup(arg+1, term-(arg+1));
164 +
165 + start = nth_line_cb(cb_data, 0);
166 +
167 + drv = userdiff_find_by_path(path);
168 + if (drv && drv->funcname.pattern) {
169 + const struct userdiff_funcname *pe = &drv->funcname;
170 + xecfg = xcalloc(1, sizeof(*xecfg));
171 + xdiff_set_find_func(xecfg, pe->pattern, pe->cflags);
172 + }
173 +
174 + reg_error = regcomp(&regexp, pattern, REG_NEWLINE);
175 + if (reg_error) {
176 + char errbuf[1024];
177 + regerror(reg_error, &regexp, errbuf, 1024);
178 + die("-L parameter '%s': %s", pattern, errbuf);
179 + }
180 +
181 + p = find_funcname_matching_regexp(xecfg, (char*) start, &regexp);
182 + if (!p)
183 + die("-L parameter '%s': no match", pattern);
184 + *begin = 0;
185 + while (p > nth_line_cb(cb_data, *begin))
186 + (*begin)++;
187 +
188 + if (*begin >= lines)
189 + die("-L parameter '%s' matches at EOF", pattern);
190 +
191 + *end = *begin+1;
192 + while (*end < lines) {
193 + const char *bol = nth_line_cb(cb_data, *end);
194 + const char *eol = nth_line_cb(cb_data, *end+1);
195 + if (match_funcname(xecfg, bol, eol))
196 + break;
197 + (*end)++;
198 + }
199 +
200 + regfree(&regexp);
201 + free(xecfg);
202 + free(pattern);
203 +
204 + /* compensate for 1-based numbering */
205 + (*begin)++;
206 +
207 + return term;
208 +}
209 +
210 int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
88 - void *cb_data, long lines, long *begin, long *end)
211 + void *cb_data, long lines, long *begin, long *end,
212 + const char *path)
213 {
214 + if (*arg == ':') {
215 + arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path);
216 + if (!arg || *arg)
217 + return -1;
218 + return 0;
219 + }
220 +
221 arg = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin);
222
223 if (*arg == ',')
@@ -100,6 +231,9 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
231
232 const char *skip_range_arg(const char *arg)
233 {
234 + if (*arg == ':')
235 + return parse_range_funcname(arg, NULL, NULL, 0, NULL, NULL, NULL);
236 +
237 arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
238
239 if (*arg == ',')
line-range.h
+2 -1
@@ -19,7 +19,8 @@ typedef const char *(*nth_line_fn_t)(void *data, long lno);
19 extern int parse_range_arg(const char *arg,
20 nth_line_fn_t nth_line_cb,
21 void *cb_data, long lines,
22 - long *begin, long *end);
22 + long *begin, long *end,
23 + const char *path);
24
25 /*
26 * Scan past a range argument that could be parsed by
t/t4211-line-log.sh
+4
@@ -25,8 +25,10 @@ test_bad_opts () {
25 canned_test "-L 4,12:a.c simple" simple-f
26 canned_test "-L 4,+9:a.c simple" simple-f
27 canned_test "-L '/long f/,/^}/:a.c' simple" simple-f
28 +canned_test "-L :f:a.c simple" simple-f-to-main
29
30 canned_test "-L '/main/,/^}/:a.c' simple" simple-main
31 +canned_test "-L :main:a.c simple" simple-main-to-end
32
33 canned_test "-L 1,+4:a.c simple" beginning-of-file
34
@@ -45,5 +47,7 @@ test_bad_opts "-L 1:simple" "There is no path"
47 test_bad_opts "-L '/foo:b.c'" "argument.*not of the form"
48 test_bad_opts "-L 1000:b.c" "has only.*lines"
49 test_bad_opts "-L 1,1000:b.c" "has only.*lines"
50 +test_bad_opts "-L :b.c" "argument.*not of the form"
51 +test_bad_opts "-L :foo:b.c" "no match"
52
53 test_done
t/t4211/expect.simple-f-to-main new
+100
@@ -0,0 +1,100 @@
1 +commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00
2 +Author: Thomas Rast <trast@student.ethz.ch>
3 +Date: Thu Feb 28 10:45:41 2013 +0100
4 +
5 + touch comment
6 +
7 +diff --git a/a.c b/a.c
8 +--- a/a.c
9 ++++ b/a.c
10 +@@ -3,14 +3,14 @@
11 + long f(long x)
12 + {
13 + int s = 0;
14 + while (x) {
15 + x >>= 1;
16 + s++;
17 + }
18 + return s;
19 + }
20 +
21 + /*
22 +- * A comment.
23 ++ * This is only an example!
24 + */
25 +
26 +
27 +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
28 +Author: Thomas Rast <trast@student.ethz.ch>
29 +Date: Thu Feb 28 10:45:16 2013 +0100
30 +
31 + touch both functions
32 +
33 +diff --git a/a.c b/a.c
34 +--- a/a.c
35 ++++ b/a.c
36 +@@ -3,14 +3,14 @@
37 +-int f(int x)
38 ++long f(long x)
39 + {
40 + int s = 0;
41 + while (x) {
42 + x >>= 1;
43 + s++;
44 + }
45 + return s;
46 + }
47 +
48 + /*
49 + * A comment.
50 + */
51 +
52 +
53 +commit f04fb20f2c77850996cba739709acc6faecc58f7
54 +Author: Thomas Rast <trast@student.ethz.ch>
55 +Date: Thu Feb 28 10:44:55 2013 +0100
56 +
57 + change f()
58 +
59 +diff --git a/a.c b/a.c
60 +--- a/a.c
61 ++++ b/a.c
62 +@@ -3,13 +3,14 @@
63 + int f(int x)
64 + {
65 + int s = 0;
66 + while (x) {
67 + x >>= 1;
68 + s++;
69 + }
70 ++ return s;
71 + }
72 +
73 + /*
74 + * A comment.
75 + */
76 +
77 +
78 +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
79 +Author: Thomas Rast <trast@student.ethz.ch>
80 +Date: Thu Feb 28 10:44:48 2013 +0100
81 +
82 + initial
83 +
84 +diff --git a/a.c b/a.c
85 +--- /dev/null
86 ++++ b/a.c
87 +@@ -0,0 +3,13 @@
88 ++int f(int x)
89 ++{
90 ++ int s = 0;
91 ++ while (x) {
92 ++ x >>= 1;
93 ++ s++;
94 ++ }
95 ++}
96 ++
97 ++/*
98 ++ * A comment.
99 ++ */
100 ++
t/t4211/expect.simple-main-to-end new
+70
@@ -0,0 +1,70 @@
1 +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83
2 +Author: Thomas Rast <trast@student.ethz.ch>
3 +Date: Thu Feb 28 10:48:43 2013 +0100
4 +
5 + change back to complete line
6 +
7 +diff --git a/a.c b/a.c
8 +--- a/a.c
9 ++++ b/a.c
10 +@@ -18,5 +18,7 @@
11 + int main ()
12 + {
13 + printf("%ld\n", f(15));
14 + return 0;
15 +-}
16 +\ No newline at end of file
17 ++}
18 ++
19 ++/* incomplete lines are bad! */
20 +
21 +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
22 +Author: Thomas Rast <trast@student.ethz.ch>
23 +Date: Thu Feb 28 10:48:10 2013 +0100
24 +
25 + change to an incomplete line at end
26 +
27 +diff --git a/a.c b/a.c
28 +--- a/a.c
29 ++++ b/a.c
30 +@@ -18,5 +18,5 @@
31 + int main ()
32 + {
33 + printf("%ld\n", f(15));
34 + return 0;
35 +-}
36 ++}
37 +\ No newline at end of file
38 +
39 +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2
40 +Author: Thomas Rast <trast@student.ethz.ch>
41 +Date: Thu Feb 28 10:45:16 2013 +0100
42 +
43 + touch both functions
44 +
45 +diff --git a/a.c b/a.c
46 +--- a/a.c
47 ++++ b/a.c
48 +@@ -17,5 +17,5 @@
49 + int main ()
50 + {
51 +- printf("%d\n", f(15));
52 ++ printf("%ld\n", f(15));
53 + return 0;
54 + }
55 +
56 +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a
57 +Author: Thomas Rast <trast@student.ethz.ch>
58 +Date: Thu Feb 28 10:44:48 2013 +0100
59 +
60 + initial
61 +
62 +diff --git a/a.c b/a.c
63 +--- /dev/null
64 ++++ b/a.c
65 +@@ -0,0 +16,5 @@
66 ++int main ()
67 ++{
68 ++ printf("%d\n", f(15));
69 ++ return 0;
70 ++}