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>
This commit is contained in:
parent
12da1d1f6f
commit
13b8f68c1f
@ -9,7 +9,7 @@
|
|||||||
--show-stats::
|
--show-stats::
|
||||||
Include additional statistics at the end of blame output.
|
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
|
Annotate only the given line range. <start> and <end> can take
|
||||||
one of these forms:
|
one of these forms:
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@ git-blame - Show what revision and author last modified each line of a file
|
|||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[verse]
|
||||||
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] [-L n,m]
|
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
|
||||||
[-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] [--abbrev=<n>]
|
[-L n,m | -L :fn] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
|
||||||
[<rev> | --contents <file> | --reverse <rev>] [--] <file>
|
[--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file>
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
|
@ -69,12 +69,13 @@ produced by --stat etc.
|
|||||||
Note that only message is considered, if also a diff is shown
|
Note that only message is considered, if also a diff is shown
|
||||||
its size is not included.
|
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>"
|
Trace the evolution of the line range given by "<start>,<end>"
|
||||||
within the <file>. You may not give any pathspec limiters.
|
(or the funcname regex <regex>) within the <file>. You may
|
||||||
This is currently limited to a walk starting from a single
|
not give any pathspec limiters. This is currently limited to
|
||||||
revision, i.e., you may only give zero or one positive
|
a walk starting from a single revision, i.e., you may only
|
||||||
revision arguments.
|
give zero or one positive revision arguments.
|
||||||
|
|
||||||
<start> and <end> can take one of these forms:
|
<start> and <end> can take one of these forms:
|
||||||
|
|
||||||
|
@ -16,3 +16,10 @@ starting at the line given by <start>.
|
|||||||
This is only valid for <end> and will specify a number
|
This is only valid for <end> and will specify a number
|
||||||
of lines before or after the line given by <start>.
|
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.
|
||||||
|
+
|
||||||
|
@ -1940,7 +1940,7 @@ static void prepare_blame_range(struct scoreboard *sb,
|
|||||||
long lno,
|
long lno,
|
||||||
long *bottom, long *top)
|
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);
|
usage(blame_usage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "strbuf.h"
|
#include "strbuf.h"
|
||||||
#include "log-tree.h"
|
#include "log-tree.h"
|
||||||
#include "graph.h"
|
#include "graph.h"
|
||||||
|
#include "userdiff.h"
|
||||||
#include "line-log.h"
|
#include "line-log.h"
|
||||||
|
|
||||||
static void range_set_grow(struct range_set *rs, size_t extra)
|
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;
|
*touched_out = touched;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static struct commit *check_single_commit(struct rev_info *revs)
|
static struct commit *check_single_commit(struct rev_info *revs)
|
||||||
{
|
{
|
||||||
struct object *commit = NULL;
|
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;
|
cb_data.line_ends = ends;
|
||||||
|
|
||||||
if (parse_range_arg(range_part, nth_line, &cb_data,
|
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);
|
die("malformed -L argument '%s'", range_part);
|
||||||
if (begin < 1)
|
if (begin < 1)
|
||||||
begin = 1;
|
begin = 1;
|
||||||
|
138
line-range.c
138
line-range.c
@ -1,5 +1,8 @@
|
|||||||
#include "git-compat-util.h"
|
#include "git-compat-util.h"
|
||||||
#include "line-range.h"
|
#include "line-range.h"
|
||||||
|
#include "xdiff-interface.h"
|
||||||
|
#include "strbuf.h"
|
||||||
|
#include "userdiff.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse one item in the -L option
|
* 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,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
|
static int match_funcname(xdemitconf_t *xecfg, const char *bol, const char *eol)
|
||||||
void *cb_data, long lines, long *begin, long *end)
|
|
||||||
{
|
{
|
||||||
|
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(®exp, pattern, REG_NEWLINE);
|
||||||
|
if (reg_error) {
|
||||||
|
char errbuf[1024];
|
||||||
|
regerror(reg_error, ®exp, errbuf, 1024);
|
||||||
|
die("-L parameter '%s': %s", pattern, errbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
p = find_funcname_matching_regexp(xecfg, (char*) start, ®exp);
|
||||||
|
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(®exp);
|
||||||
|
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,
|
||||||
|
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);
|
arg = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin);
|
||||||
|
|
||||||
if (*arg == ',')
|
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)
|
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);
|
arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
|
||||||
|
|
||||||
if (*arg == ',')
|
if (*arg == ',')
|
||||||
|
@ -19,7 +19,8 @@ typedef const char *(*nth_line_fn_t)(void *data, long lno);
|
|||||||
extern int parse_range_arg(const char *arg,
|
extern int parse_range_arg(const char *arg,
|
||||||
nth_line_fn_t nth_line_cb,
|
nth_line_fn_t nth_line_cb,
|
||||||
void *cb_data, long lines,
|
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
|
* Scan past a range argument that could be parsed by
|
||||||
|
@ -25,8 +25,10 @@ test_bad_opts () {
|
|||||||
canned_test "-L 4,12:a.c simple" simple-f
|
canned_test "-L 4,12:a.c simple" simple-f
|
||||||
canned_test "-L 4,+9: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 '/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
|
||||||
|
canned_test "-L :main:a.c simple" simple-main-to-end
|
||||||
|
|
||||||
canned_test "-L 1,+4:a.c simple" beginning-of-file
|
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 '/foo:b.c'" "argument.*not of the form"
|
||||||
test_bad_opts "-L 1000:b.c" "has only.*lines"
|
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 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
|
test_done
|
||||||
|
100
t/t4211/expect.simple-f-to-main
Normal file
100
t/t4211/expect.simple-f-to-main
Normal file
@ -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.
|
||||||
|
+ */
|
||||||
|
+
|
70
t/t4211/expect.simple-main-to-end
Normal file
70
t/t4211/expect.simple-main-to-end
Normal file
@ -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;
|
||||||
|
+}
|
Loading…
Reference in New Issue
Block a user