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