Implement line-history search (git log -L)
[git] / line-range.c
1 #include "git-compat-util.h"
2 #include "line-range.h"
3
4 /*
5  * Parse one item in the -L option
6  */
7 static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
8                              void *data, long lines, long begin, long *ret)
9 {
10         char *term;
11         const char *line;
12         long num;
13         int reg_error;
14         regex_t regexp;
15         regmatch_t match[1];
16
17         /* Allow "-L <something>,+20" to mean starting at <something>
18          * for 20 lines, or "-L <something>,-5" for 5 lines ending at
19          * <something>.
20          */
21         if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
22                 num = strtol(spec + 1, &term, 10);
23                 if (term != spec + 1) {
24                         if (!ret)
25                                 return term;
26                         if (spec[0] == '-')
27                                 num = 0 - num;
28                         if (0 < num)
29                                 *ret = begin + num - 2;
30                         else if (!num)
31                                 *ret = begin;
32                         else
33                                 *ret = begin + num;
34                         return term;
35                 }
36                 return spec;
37         }
38         num = strtol(spec, &term, 10);
39         if (term != spec) {
40                 if (ret)
41                         *ret = num;
42                 return term;
43         }
44         if (spec[0] != '/')
45                 return spec;
46
47         /* it could be a regexp of form /.../ */
48         for (term = (char *) spec + 1; *term && *term != '/'; term++) {
49                 if (*term == '\\')
50                         term++;
51         }
52         if (*term != '/')
53                 return spec;
54
55         /* in the scan-only case we are not interested in the regex */
56         if (!ret)
57                 return term+1;
58
59         /* try [spec+1 .. term-1] as regexp */
60         *term = 0;
61         begin--; /* input is in human terms */
62         line = nth_line(data, begin);
63
64         if (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&
65             !(reg_error = regexec(&regexp, line, 1, match, 0))) {
66                 const char *cp = line + match[0].rm_so;
67                 const char *nline;
68
69                 while (begin++ < lines) {
70                         nline = nth_line(data, begin);
71                         if (line <= cp && cp < nline)
72                                 break;
73                         line = nline;
74                 }
75                 *ret = begin;
76                 regfree(&regexp);
77                 *term++ = '/';
78                 return term;
79         }
80         else {
81                 char errbuf[1024];
82                 regerror(reg_error, &regexp, errbuf, 1024);
83                 die("-L parameter '%s': %s", spec + 1, errbuf);
84         }
85 }
86
87 int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
88                     void *cb_data, long lines, long *begin, long *end)
89 {
90         arg = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin);
91
92         if (*arg == ',')
93                 arg = parse_loc(arg + 1, nth_line_cb, cb_data, lines, *begin + 1, end);
94
95         if (*arg)
96                 return -1;
97
98         return 0;
99 }
100
101 const char *skip_range_arg(const char *arg)
102 {
103         arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
104
105         if (*arg == ',')
106                 arg = parse_loc(arg+1, NULL, NULL, 0, 0, NULL);
107
108         return arg;
109 }