Skip to content

Commit 13b8f68

Browse files
trastgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 12da1d1 commit 13b8f68

File tree

11 files changed

+332
-14
lines changed

11 files changed

+332
-14
lines changed

Documentation/blame-options.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
--show-stats::
1010
Include additional statistics at the end of blame output.
1111

12-
-L <start>,<end>::
12+
-L <start>,<end>, -L :<regex>::
1313
Annotate only the given line range. <start> and <end> can take
1414
one of these forms:
1515

Documentation/git-blame.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ git-blame - Show what revision and author last modified each line of a file
88
SYNOPSIS
99
--------
1010
[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>
1414

1515
DESCRIPTION
1616
-----------

Documentation/git-log.txt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,13 @@ produced by --stat etc.
6969
Note that only message is considered, if also a diff is shown
7070
its size is not included.
7171

72-
-L <start>,<end>:<file>::
72+
-L <start>,<end>:<file>, -L :<regex>:<file>::
73+
7374
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.
7879

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

Documentation/line-range-format.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,10 @@ starting at the line given by <start>.
1616
This is only valid for <end> and will specify a number
1717
of lines before or after the line given by <start>.
1818
+
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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1940,7 +1940,7 @@ static void prepare_blame_range(struct scoreboard *sb,
19401940
long lno,
19411941
long *bottom, long *top)
19421942
{
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))
19441944
usage(blame_usage);
19451945
}
19461946

line-log.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "strbuf.h"
1313
#include "log-tree.h"
1414
#include "graph.h"
15+
#include "userdiff.h"
1516
#include "line-log.h"
1617

1718
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,
438439
*touched_out = touched;
439440
}
440441

441-
442442
static struct commit *check_single_commit(struct rev_info *revs)
443443
{
444444
struct object *commit = NULL;
@@ -559,7 +559,8 @@ parse_lines(struct commit *commit, const char *prefix, struct string_list *args)
559559
cb_data.line_ends = ends;
560560

561561
if (parse_range_arg(range_part, nth_line, &cb_data,
562-
lines, &begin, &end))
562+
lines, &begin, &end,
563+
spec->path))
563564
die("malformed -L argument '%s'", range_part);
564565
if (begin < 1)
565566
begin = 1;

line-range.c

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#include "git-compat-util.h"
22
#include "line-range.h"
3+
#include "xdiff-interface.h"
4+
#include "strbuf.h"
5+
#include "userdiff.h"
36

47
/*
58
* 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,
8487
}
8588
}
8689

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+
87210
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)
89213
{
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+
90221
arg = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin);
91222

92223
if (*arg == ',')
@@ -100,6 +231,9 @@ int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
100231

101232
const char *skip_range_arg(const char *arg)
102233
{
234+
if (*arg == ':')
235+
return parse_range_funcname(arg, NULL, NULL, 0, NULL, NULL, NULL);
236+
103237
arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
104238

105239
if (*arg == ',')

line-range.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ typedef const char *(*nth_line_fn_t)(void *data, long lno);
1919
extern int parse_range_arg(const char *arg,
2020
nth_line_fn_t nth_line_cb,
2121
void *cb_data, long lines,
22-
long *begin, long *end);
22+
long *begin, long *end,
23+
const char *path);
2324

2425
/*
2526
* Scan past a range argument that could be parsed by

t/t4211-line-log.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ test_bad_opts () {
2525
canned_test "-L 4,12:a.c simple" simple-f
2626
canned_test "-L 4,+9:a.c simple" simple-f
2727
canned_test "-L '/long f/,/^}/:a.c' simple" simple-f
28+
canned_test "-L :f:a.c simple" simple-f-to-main
2829

2930
canned_test "-L '/main/,/^}/:a.c' simple" simple-main
31+
canned_test "-L :main:a.c simple" simple-main-to-end
3032

3133
canned_test "-L 1,+4:a.c simple" beginning-of-file
3234

@@ -45,5 +47,7 @@ test_bad_opts "-L 1:simple" "There is no path"
4547
test_bad_opts "-L '/foo:b.c'" "argument.*not of the form"
4648
test_bad_opts "-L 1000:b.c" "has only.*lines"
4749
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"
4852

4953
test_done

t/t4211/expect.simple-f-to-main

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00
2+
Author: Thomas Rast <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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+
+

0 commit comments

Comments
 (0)