a4f12b151d2924386b5089dd1183853602d60975
[bpt/coccinelle.git] / tools / gitgrep.ml
1 (*
2 * Copyright 2010, INRIA, University of Copenhagen
3 * Julia Lawall, Rene Rydhof Hansen, Gilles Muller, Nicolas Palix
4 * Copyright 2005-2009, Ecole des Mines de Nantes, University of Copenhagen
5 * Yoann Padioleau, Julia Lawall, Rene Rydhof Hansen, Henrik Stuart, Gilles Muller, Nicolas Palix
6 * This file is part of Coccinelle.
7 *
8 * Coccinelle is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, according to version 2 of the License.
11 *
12 * Coccinelle is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Coccinelle. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * The authors reserve the right to distribute this or future versions of
21 * Coccinelle under other licenses.
22 *)
23
24
25 (* adjust as convenient *)
26 let prefix = "/tmp/"
27 let prefix = ""
28
29 (* The -grouped option means that all - and + code must appear in a
30 single contiguous block of - + code. This option has no effect on the
31 other kinds of patterns, ie Changelog (C) or Context (@) *)
32
33 (* example: gitgrep -grouped -maxlen 25 - "[A-Z][A-Z]+" + "[A-Z][A-Z]+"
34 usb_21_22 *)
35
36 type dir = Minus | Plus | Context | ChangeLog
37
38 type res = Git of string | Block of int * string
39
40 let grouped = ref false
41 let maxlen = ref None
42
43 let space = Str.regexp " "
44
45 let matches pattern line =
46 try let _ = Str.search_forward pattern line 0 in true
47 with Not_found -> false
48
49 let res = ref []
50
51 let scan dir pattern i =
52 let rec loop skipping cl git =
53 let line = input_line i in
54 match Str.split space line with
55 ["commit";git] -> loop false true git
56 | "diff"::_ -> loop skipping false git
57 | _ ->
58 if String.length line > 0 && not skipping &&
59 ((String.get line 0 = '-' && dir = Minus) or
60 (String.get line 0 = '+' && dir = Plus) or
61 (cl && dir = ChangeLog) or
62 (not (String.get line 0 = '-') && not (String.get line 0 = '+') &&
63 dir = Context)) &&
64 matches pattern line
65 then (res := Git(git)::!res; loop true cl git)
66 else loop skipping cl git in
67 loop false false ""
68
69 (* for Minus and Plus directions only *)
70 let scan_grouped dir pattern i =
71 let block = ref 0 in
72 (* mp = true in minus-plus region *)
73 let rec loop mp git =
74 let line = input_line i in
75 match Str.split space line with
76 ["commit";git] -> loop false git
77 | "diff"::_ -> loop false git
78 | _ ->
79 if String.length line > 0
80 then
81 let first_char = String.get line 0 in
82 let new_mp =
83 match first_char with
84 '-' | '+' -> (if not mp then block := !block + 1; true)
85 | _ -> false in
86 match (first_char,dir) with
87 ('-',Minus) | ('+',Plus) ->
88 let info = Block(!block,git) in
89 (if matches pattern line && not (List.mem info !res)
90 then res := info::!res);
91 loop new_mp git
92 | _ -> loop new_mp git
93 else loop mp git in
94 loop false ""
95
96 let scan_line max i =
97 let rec loop skipping num git =
98 let line = input_line i in
99 match Str.split space line with
100 ["commit";git1] ->
101 loop false (-1) git1
102 | "diff"::_ ->
103 if num > max && not skipping
104 then (res:=Git(git)::!res;loop true (num+1) git)
105 else loop skipping (if num = (-1) then 1 else num+1) git
106 | _ ->
107 if num > max && not skipping
108 then (res:=Git(git)::!res;loop true (num+1) git)
109 else loop skipping (if num = (-1) then num else num+1) git in
110 loop false (-1) ""
111
112 let dot = Str.regexp "\\."
113
114 let open_git file =
115 let tmp = prefix^file in
116 if Sys.file_exists tmp
117 then open_in tmp
118 else
119 match List.rev (Str.split dot file) with
120 last::rest ->
121 let last_int = int_of_string last in
122 if last_int = 0
123 then
124 failwith
125 "can't go back one version from 0; make the log file by hand";
126 let prev =
127 String.concat "." (List.rev ((string_of_int (last_int-1))::rest)) in
128 let _ =
129 Sys.command
130 (Printf.sprintf "git log -p v%s..v%s > %s" prev file tmp) in
131 open_in tmp
132 | _ -> open_in file
133
134 let rec split_args = function
135 [] -> []
136 | "-grouped"::rest -> grouped := true; split_args rest
137 | "-maxlen"::len::rest -> maxlen := Some (int_of_string len); split_args rest
138 | "-"::pattern::rest -> (Minus,Str.regexp pattern) :: split_args rest
139 | "+"::pattern::rest -> (Plus,Str.regexp pattern) :: split_args rest
140 | "@"::pattern::rest -> (Context,Str.regexp pattern) :: split_args rest
141 | "C"::pattern::rest -> (ChangeLog,Str.regexp pattern) :: split_args rest
142 | _ -> failwith "bad argument list"
143
144 let process_one (dir,pattern) version =
145 res := [];
146 let i = open_git version in
147 try
148 if !grouped && (dir = Minus or dir = Plus)
149 then scan_grouped dir pattern i
150 else scan dir pattern i
151 with End_of_file -> (close_in i; List.rev !res)
152
153 let process_len max version =
154 res := [];
155 let i = open_git version in
156 try scan_line max i
157 with End_of_file -> (close_in i; List.rev !res)
158
159 let inter l1 l2 =
160 List.rev
161 (List.fold_left
162 (function prev ->
163 function
164 (Git(git)) as x ->
165 let rec loop = function
166 [] -> prev
167 | Git(git1)::rest when git = git1 -> x::prev
168 | Block(b1,git1)::rest when git = git1 -> Block(b1,git1)::prev
169 | _::rest -> loop rest in
170 loop l2
171 | (Block(block,git)) as x ->
172 let rec loop = function
173 [] -> prev
174 | Git(git1)::rest when git = git1 -> x::prev
175 | Block(b1,git1)::rest when block = b1 && git = git1 ->
176 Block(b1,git1)::prev
177 | _::rest -> loop rest in
178 loop l2)
179 [] l1)
180
181 let _ =
182 if Array.length Sys.argv < 4
183 then failwith "arguments: -/+/@/C pattern -/+/@/C pattern ... version";
184 let args = List.tl(Array.to_list Sys.argv) in
185 let version = List.hd(List.rev args) in
186 let pairs = List.rev(List.tl(List.rev args)) in
187 let requirements = split_args pairs in
188 let res =
189 List.map (function Git x -> x | Block (_,x) -> x)
190 (List.fold_left
191 (function all ->
192 function pattern ->
193 inter (process_one pattern version) all)
194 (process_one (List.hd requirements) version)
195 (List.tl requirements)) in
196 let res =
197 if !grouped
198 then
199 List.rev
200 (List.fold_left
201 (function prev ->
202 function x -> if List.mem x prev then prev else x::prev)
203 [] res)
204 else res in
205 let res =
206 match !maxlen with
207 None -> res
208 | Some max ->
209 let badgits = process_len max version in
210 List.filter (function x -> not(List.mem (Git(x)) badgits)) res in
211 List.iter (function name -> Printf.printf "%s\n" name) res