From 13b8f68c1f0b6d4ff5a7bed5a834e7e5e34a9fd0 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Thu, 28 Mar 2013 17:47:33 +0100 Subject: [PATCH] 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 Signed-off-by: Junio C Hamano --- Documentation/blame-options.txt | 2 +- Documentation/git-blame.txt | 6 +- Documentation/git-log.txt | 11 ++- Documentation/line-range-format.txt | 7 ++ builtin/blame.c | 2 +- line-log.c | 5 +- line-range.c | 138 +++++++++++++++++++++++++++- line-range.h | 3 +- t/t4211-line-log.sh | 4 + t/t4211/expect.simple-f-to-main | 100 ++++++++++++++++++++ t/t4211/expect.simple-main-to-end | 70 ++++++++++++++ 11 files changed, 333 insertions(+), 15 deletions(-) create mode 100644 t/t4211/expect.simple-f-to-main create mode 100644 t/t4211/expect.simple-main-to-end diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index 6998d9f5bf..e9f984ba01 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -9,7 +9,7 @@ --show-stats:: Include additional statistics at the end of blame output. --L ,:: +-L ,, -L ::: Annotate only the given line range. and can take one of these forms: diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index 9a05c2b3d2..6cea7f1ce1 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -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 ] [-M] [-C] [-C] [-C] [--since=] [--abbrev=] - [ | --contents | --reverse ] [--] +'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] + [-L n,m | -L :fn] [-S ] [-M] [-C] [-C] [-C] [--since=] + [--abbrev=] [ | --contents | --reverse ] [--] DESCRIPTION ----------- diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 8727c60ab3..4850226aaf 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -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 ,::: +-L ,:, -L :::: + Trace the evolution of the line range given by "," - within the . 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 ) within the . 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. and can take one of these forms: diff --git a/Documentation/line-range-format.txt b/Documentation/line-range-format.txt index 265bc23290..3e7ce72daa 100644 --- a/Documentation/line-range-format.txt +++ b/Documentation/line-range-format.txt @@ -16,3 +16,10 @@ starting at the line given by . This is only valid for and will specify a number of lines before or after the line given by . + + +- :regex ++ +If the option's argument is of the form :regex, it denotes the range +from the first funcname line that matches , up to the next +funcname line. ++ diff --git a/builtin/blame.c b/builtin/blame.c index 20eb439cb8..1c09d552fa 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -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); } diff --git a/line-log.c b/line-log.c index 624423142e..68972e24c7 100644 --- a/line-log.c +++ b/line-log.c @@ -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; diff --git a/line-range.c b/line-range.c index 7a7ca3c2bd..8faf943745 100644 --- a/line-range.c +++ b/line-range.c @@ -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, } } -int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb, - void *cb_data, long lines, long *begin, long *end) +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(®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); 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 == ',') diff --git a/line-range.h b/line-range.h index 88aaf08b7f..ae3d0123b4 100644 --- a/line-range.h +++ b/line-range.h @@ -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 diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh index 9f36f17384..426a82868e 100755 --- a/t/t4211-line-log.sh +++ b/t/t4211-line-log.sh @@ -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 index 0000000000..a475768710 --- /dev/null +++ b/t/t4211/expect.simple-f-to-main @@ -0,0 +1,100 @@ +commit 39b6eb2d5b706d3322184a169f666f25ed3fbd00 +Author: Thomas Rast +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 +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 +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 +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 index 0000000000..8480bd9cc4 --- /dev/null +++ b/t/t4211/expect.simple-main-to-end @@ -0,0 +1,70 @@ +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83 +Author: Thomas Rast +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 +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 +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 +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; ++}