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(®exp, pattern, REG_NEWLINE);
175
+ if (reg_error) {
176
+ char errbuf[1024];
177
+ regerror(reg_error, ®exp, errbuf, 1024);
178
+ die("-L parameter '%s': %s", pattern, errbuf);
179
+ }
180
+
181
+ p = find_funcname_matching_regexp(xecfg, (char*) start, ®exp);
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(®exp);
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
++}