permit multiline comments and strings in macros
[bpt/coccinelle.git] / tools / splitpatch.ml
CommitLineData
f537ebc4 1(*
17ba0788
C
2 * Copyright 2012, INRIA
3 * Julia Lawall, Gilles Muller
4 * Copyright 2010-2011, INRIA, University of Copenhagen
f537ebc4
C
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
feec80c3 27# 0 "./splitpatch.ml"
c491d8ee
C
28(* split patch per file *)
29
30(* ------------------------------------------------------------------------ *)
31(* The following are a reminder of what this information should look like.
32These values are not used. See the README file for information on how to
33create a .splitpatch file in your home directory. *)
34
35let from = ref "email@xyz.org"
36let git_tree = ref "/var/linuxes/linux-next"
37let git_options = ref "--cc=kernel-janitors@vger.kernel.org --suppress-cc=self"
97111a47 38let not_linux = ref "--suppress-cc=self"
c491d8ee
C
39let prefix_before = ref (Some "/var/linuxes/linux-next")
40let prefix_after = ref (Some "/var/julia/linuxcopy")
41
42(* ------------------------------------------------------------------------ *)
43(* misc *)
44
45let process_output_to_list2 = fun command ->
46 let chan = Unix.open_process_in command in
47 let res = ref ([] : string list) in
48 let rec process_otl_aux () =
49 let e = input_line chan in
50 res := e::!res;
51 process_otl_aux() in
52 try process_otl_aux ()
53 with End_of_file ->
54 let stat = Unix.close_process_in chan in (List.rev !res,stat)
55let cmd_to_list command =
56 let (l,_) = process_output_to_list2 command in l
57let process_output_to_list = cmd_to_list
58let cmd_to_list_and_status = process_output_to_list2
59
60let safe_chop_extension s = try Filename.chop_extension s with _ -> s
61
62let safe_get_extension s =
63 match List.rev (Str.split (Str.regexp_string ".") s) with
64 ext::_::rest -> Some (String.concat "." (List.rev rest))
65 | _ -> None
66
67(* ------------------------------------------------------------------------ *)
68(* set configuration variables *)
69
70let from_from_template template =
71 let signed_offs =
72 cmd_to_list (Printf.sprintf "grep Signed-off-by: %s" template) in
73 match signed_offs with
74 x::xs -> String.concat " " (Str.split (Str.regexp "[ \t]+") x)
75 | _ -> failwith "No Signed-off-by in template file"
76
77let from_from_gitconfig path =
78 let config = path^"/.git/config" in
79 if Sys.file_exists config
80 then
81 let i = open_in config in
82 let rec inner_loop _ =
83 let l = input_line i in
84 match Str.split (Str.regexp "[ \t]+") l with
85 "from"::"="::f -> from := String.concat " " f
86 | _ ->
87 if String.length l >= 1 && String.get l 0 = '['
88 then ()
89 else inner_loop() in
90 let rec outer_loop _ =
91 let l = input_line i in
92 if l = "[sendemail]"
93 then inner_loop()
94 else outer_loop() in
95 (try outer_loop() with Not_found -> ());
96 close_in i
97
98let read_configs template =
99 let temporary_git_tree = ref None in
100 git_options := "";
101 prefix_before := None;
102 prefix_after := None;
103 (* get information in message template, lowest priority *)
104 from := from_from_template template;
105 (* get information in git config *)
106 let rec loop = function
107 "/" -> ()
108 | path ->
109 if Sys.file_exists ".git"
110 then
111 begin temporary_git_tree := Some path; from_from_gitconfig path end
112 else loop (Filename.dirname path) in
113 loop (Sys.getcwd());
114 (* get information from .splitpatch *)
115 let home = List.hd(cmd_to_list "ls -d ~") in
116 let config = home^"/.splitpatch" in
117 (if Sys.file_exists config
118 then
119 let i = open_in config in
120 let rec loop _ =
121 let l = input_line i in
122 (* bounded split doesn't split at = in value part *)
123 (match Str.bounded_split (Str.regexp "[ \t]*=[ \t]*") l 2 with
124 ["from";s] -> from := s
125 | ["git_tree";s] -> temporary_git_tree := Some s
97111a47 126 | ["git_options";s] -> git_options := s; not_linux := s
c491d8ee
C
127 | ["prefix_before";s] -> prefix_before := Some s
128 | ["prefix_after";s] -> prefix_after := Some s
129 | _ -> Printf.fprintf stderr "unknown line: %s\n" l);
130 loop() in
131 try loop() with End_of_file -> close_in i);
132 match !temporary_git_tree with
133 None -> failwith "Unable to find Linux source tree"
134 | Some g -> git_tree := g
135
136(* ------------------------------------------------------------------------ *)
137
138let maintainer_command file =
139 Printf.sprintf
feec80c3 140 "cd %s; scripts/get_maintainer.pl --nokeywords --separator , --nogit-fallback --norolestats -f %s"
c491d8ee
C
141 !git_tree file
142
143let subsystem_command file =
144 Printf.sprintf
feec80c3 145 "cd %s; scripts/get_maintainer.pl --nokeywords --nogit-fallback --subsystem --norolestats -f %s | grep -v @"
c491d8ee
C
146 !git_tree file
147
148let checkpatch_command file =
149 Printf.sprintf "cd %s; scripts/checkpatch.pl %s" !git_tree file
150
151let default_string = "THE REST" (* split by file *)
152
153(* ------------------------------------------------------------------------ *)
154(* ------------------------------------------------------------------------ *)
155(* Template file processing *)
156
157let read_up_to_dashes i =
158 let lines = ref [] in
159 let rec loop _ =
160 let l = input_line i in
161 if l = "---"
162 then ()
163 else begin lines := l :: !lines; loop() end in
164 (try loop() with End_of_file -> ());
165 let lines =
166 match !lines with
167 ""::lines -> List.rev lines (* drop last line if blank *)
168 | lines -> List.rev lines in
169 match lines with
170 ""::lines -> lines (* drop first line if blank *)
171 | _ -> lines
172
173let get_template_information file =
174 let i = open_in file in
175 (* subject *)
176 let subject = read_up_to_dashes i in
177 match subject with
178 [subject] ->
179 let cover = read_up_to_dashes i in
180 let message = read_up_to_dashes i in
181 if message = []
182 then (subject,None,cover)
183 else (subject,Some cover,message)
5626f154
C
184 | _ ->
185 failwith
186 ("Subject must be exactly one line "^
187 (string_of_int (List.length subject)))
c491d8ee
C
188
189(* ------------------------------------------------------------------------ *)
190(* ------------------------------------------------------------------------ *)
191(* Patch processing *)
192
193let spaces = Str.regexp "[ \t]+"
194
195let fix_before_after l prefix = function
196 Some old_prefix ->
197 (match Str.split spaces l with
198 ("diff"|"+++"|"---")::_ ->
199 (match Str.split (Str.regexp old_prefix) l with
200 [a;b] ->
201 (match Str.split_delim (Str.regexp ("[ \t]"^prefix)) a with
202 [_;""] -> a^b (* prefix is already there *)
203 | _ -> a^prefix^b)
204 | _ -> l)
205 | _ -> l)
206 | _ -> l
207
208let fix_date l =
209 match Str.split spaces l with
210 (("+++"|"---") as a)::path::rest -> Printf.sprintf "%s %s" a path
211 | _ -> l
212
213(* ------------------------------------------------------------------------ *)
214
215let is_diff = Str.regexp "diff "
216let split_patch i =
217 let patches = ref [] in
218 let cur = ref [] in
219 let get_size l =
220 match Str.split_delim (Str.regexp ",") l with
221 [_;size] -> int_of_string size
1eddfd50 222 | [_] -> 1
c491d8ee
C
223 | _ -> failwith ("bad size: "^l) in
224 let rec read_diff_or_atat _ =
225 let l = input_line i in
226 let l = fix_date(fix_before_after l "a" !prefix_before) in
227 let l = fix_date(fix_before_after l "b" !prefix_after) in
228 match Str.split spaces l with
229 "diff"::_ ->
230 (if List.length !cur > 0
231 then patches := List.rev !cur :: !patches);
232 cur := [l];
233 read_diff()
234 | "@@"::min::pl::"@@"::rest ->
235 let msize = get_size min in
236 let psize = get_size pl in
237 cur := l :: !cur;
238 read_hunk msize psize
239 | "\\"::_ -> cur := l :: !cur; read_diff_or_atat()
240 | _ ->
241 failwith
242 "expected diff or @@ (diffstat information should not be present)"
243 and read_diff _ =
244 let l = input_line i in
245 let l = fix_date(fix_before_after l "a" !prefix_before) in
246 let l = fix_date(fix_before_after l "b" !prefix_after) in
247 cur := l :: !cur;
248 match Str.split spaces l with
249 "+++"::_ -> read_diff_or_atat()
250 | _ -> read_diff()
251 and read_hunk msize psize =
252 if msize = 0 && psize = 0
253 then read_diff_or_atat()
254 else
255 let l = input_line i in
256 cur := l :: !cur;
257 match String.get l 0 with
258 '-' -> read_hunk (msize - 1) psize
259 | '+' -> read_hunk msize (psize - 1)
260 | _ -> read_hunk (msize - 1) (psize - 1) in
261 try read_diff_or_atat()
262 with End_of_file -> List.rev ((List.rev !cur)::!patches)
263
264(* ------------------------------------------------------------------------ *)
265
97111a47
C
266let uctr = ref 0
267
268let found_a_maintainer = ref false
269
755320b0
C
270let common_prefix l1 l2 =
271 let rec loop = function
272 ([],_) | (_,[]) -> []
273 | (x::xs,y::ys) when x = y -> x :: (loop (xs,ys))
274 | _ -> [] in
275 match loop (l1,l2) with
276 [] -> None
277 | res -> Some (String.concat "/" res)
278
279let find_common_path file cell =
280 let fs = Str.split (Str.regexp "/") file in
281 let rec loop = function
282 [] ->
283 let c1 = ref [] in
284 cell := ((ref file),c1)::!cell;
285 c1
286 | (f,c1)::xs ->
287 (match common_prefix fs (Str.split (Str.regexp "/") !f) with
288 None -> loop xs
289 | Some cp -> f := cp; c1) in
290 loop !cell
291
c491d8ee
C
292let resolve_maintainers patches =
293 let maintainer_table = Hashtbl.create (List.length patches) in
294 List.iter
295 (function
296 diff_line::rest ->
297 (match Str.split (Str.regexp " a/") diff_line with
298 [before;after] ->
299 (match Str.split spaces after with
300 file::_ ->
301 let maintainers =
97111a47
C
302 match (cmd_to_list (maintainer_command file)) with
303 m::_ -> found_a_maintainer := true; m
304 | [] ->
305 (* maybe the file is new? *)
306 (match
307 (cmd_to_list
308 (maintainer_command (Filename.dirname file)))
309 with
310 m::_ -> found_a_maintainer := true; m
311 | [] ->
312 uctr := !uctr + 1;
313 "unknown"^(string_of_int !uctr)) in
c491d8ee
C
314 let subsystems =
315 cmd_to_list (subsystem_command file) in
316 let info = (subsystems,maintainers) in
317 let cell =
318 try Hashtbl.find maintainer_table info
319 with Not_found ->
320 let cell = ref [] in
321 Hashtbl.add maintainer_table info cell;
322 cell in
755320b0
C
323 let cell1 = find_common_path file cell in
324 cell1 := (file,(diff_line :: rest)) :: !cell1
c491d8ee
C
325 | _ -> failwith "filename not found")
326 | _ ->
327 failwith (Printf.sprintf "prefix a/ not found in %s" diff_line))
328 | _ -> failwith "bad diff line")
329 patches;
330 maintainer_table
331
332(* ------------------------------------------------------------------------ *)
333
c491d8ee
C
334let print_all o l =
335 List.iter (function x -> Printf.fprintf o "%s\n" x) l
336
337let make_mail_header o date maintainers ctr number subject =
338 Printf.fprintf o "From nobody %s\n" date;
339 Printf.fprintf o "From: %s\n" !from;
340 (match Str.split (Str.regexp_string ",") maintainers with
341 [x] -> Printf.fprintf o "To: %s\n" x
342 | x::xs ->
343 Printf.fprintf o "To: %s\n" x;
344 Printf.fprintf o "Cc: %s\n" (String.concat "," xs)
345 | _ -> failwith "no maintainers");
346 if number = 1
347 then Printf.fprintf o "Subject: [PATCH] %s\n\n" subject
348 else Printf.fprintf o "Subject: [PATCH %d/%d] %s\n\n" ctr number subject
349
350let make_message_files subject cover message date maintainer_table
755320b0 351 patch front add_ext nomerge =
c491d8ee
C
352 let ctr = ref 0 in
353 let elements =
354 Hashtbl.fold
355 (function (services,maintainers) ->
356 function diffs ->
357 function rest ->
755320b0 358 if services=[default_string] or nomerge
c491d8ee
C
359 then
360 (* if no maintainer, then one file per diff *)
755320b0
C
361 let diffs =
362 List.concat
363 (List.map (function (common,diffs) -> !diffs) !diffs) in
c491d8ee
C
364 (List.map
365 (function (file,diff) ->
366 ctr := !ctr + 1;
755320b0
C
367 (file,(!ctr,true,maintainers,[file],[diff])))
368 (List.rev diffs)) @
c491d8ee
C
369 rest
370 else
755320b0
C
371 (List.map
372 (function (common,diffs) ->
373 ctr := !ctr + 1;
374 let (files,diffs) = List.split (List.rev !diffs) in
375 (!common,(!ctr,false,maintainers,files,diffs)))
376 !diffs) @
377 rest)
c491d8ee
C
378 maintainer_table [] in
379 let number = List.length elements in
380 let generated =
381 List.map
755320b0 382 (function (common,(ctr,the_rest,maintainers,files,diffs)) ->
c491d8ee
C
383 let output_file = add_ext(Printf.sprintf "%s%d" front ctr) in
384 let o = open_out output_file in
385 make_mail_header o date maintainers ctr number
755320b0 386 (Printf.sprintf "%s: %s" common subject);
c491d8ee
C
387 print_all o message;
388 Printf.fprintf o "\n---\n";
389 let (nm,o1) = Filename.open_temp_file "patch" "patch" in
390 List.iter (print_all o1) (List.rev diffs);
391 close_out o1;
392 let diffstat =
393 cmd_to_list
394 (Printf.sprintf "diffstat -p1 < %s ; /bin/rm %s" nm nm) in
395 List.iter (print_all o) [diffstat];
396 Printf.fprintf o "\n";
397 List.iter (print_all o) diffs;
398 Printf.fprintf o "\n";
399 close_out o;
400 let (info,stat) =
401 cmd_to_list_and_status
402 (checkpatch_command ((Sys.getcwd())^"/"^output_file)) in
403 (if not(stat = Unix.WEXITED 0)
404 then (print_all stderr info; Printf.fprintf stderr "\n"));
405 output_file)
406 (List.rev elements) in
407 let later = add_ext(Printf.sprintf "%s%d" front (number+1)) in
408 if Sys.file_exists later
409 then Printf.fprintf stderr "Warning: %s and other files may be left over from a previous run\n" later;
410 generated
411
412let make_cover_file n subject cover front date maintainer_table =
413 match cover with
414 None -> ()
415 | Some cover ->
416 let common_maintainers =
417 let intersect l1 l2 =
418 List.rev
419 (List.fold_left
420 (function i -> function cur ->
421 if List.mem cur l2 then cur :: i else i)
422 [] l1) in
423 let start = ref true in
424 String.concat ","
425 (Hashtbl.fold
426 (function (services,maintainers) ->
427 function diffs ->
428 function rest ->
429 let cur = Str.split (Str.regexp_string ",") maintainers in
430 if !start
431 then begin start := false; cur end
432 else intersect cur rest)
433 maintainer_table []) in
434 let output_file = Printf.sprintf "%s.cover" front in
435 let o = open_out output_file in
436 make_mail_header o date common_maintainers 0 n subject;
437 print_all o cover;
438 Printf.fprintf o "\n";
439 close_out o
440
441let mail_sender = "git send-email" (* use this when it works *)
442let mail_sender = "cocci-send-email.perl"
443
444let generate_command front cover generated =
445 let output_file = front^".cmd" in
446 let o = open_out output_file in
447 (match cover with
448 None ->
449 Printf.fprintf o
450 "%s --auto-to --no-thread --from=\"%s\" %s $* %s\n"
451 mail_sender !from !git_options
452 (String.concat " " generated)
453 | Some cover ->
454 Printf.fprintf o
455 "%s --auto-to --thread --from=\"%s\" %s $* %s\n"
456 mail_sender !from !git_options
457 (String.concat " " ((front^".cover") :: generated)));
458 close_out o
459
755320b0 460let make_output_files subject cover message maintainer_table patch nomerge =
c491d8ee
C
461 let date = List.hd (cmd_to_list "date") in
462 let front = safe_chop_extension patch in
463 let add_ext =
464 match safe_get_extension patch with
465 Some ext -> (function s -> s ^ "." ^ ext)
466 | None -> (function s -> s) in
467 let generated =
468 make_message_files subject cover message date maintainer_table
755320b0 469 patch front add_ext nomerge in
c491d8ee
C
470 make_cover_file (List.length generated) subject cover front date
471 maintainer_table;
472 generate_command front cover generated
473
474(* ------------------------------------------------------------------------ *)
475
755320b0
C
476let nomerge = ref false
477
c491d8ee
C
478let parse_args l =
479 let (other_args,files) =
755320b0 480 List.partition (function a -> String.length a > 1 && String.get a 0 = '-')
c491d8ee 481 l in
755320b0
C
482 let (nomergep,other_args) =
483 List.partition (function a -> a = "-nomerge") other_args in
484 (if not(nomergep = []) then nomerge := true);
c491d8ee
C
485 match files with
486 [file] -> (file,String.concat " " other_args)
487 | _ -> failwith "Only one file allowed"
488
489let _ =
490 let (file,git_args) = parse_args (List.tl (Array.to_list Sys.argv)) in
491 let message_file = (safe_chop_extension file)^".msg" in
492 (* set up environment *)
493 read_configs message_file;
c491d8ee
C
494 (* get message information *)
495 let (subject,cover,message) = get_template_information message_file in
496 (* split patch *)
497 let i = open_in file in
498 let patches = split_patch i in
499 close_in i;
500 let maintainer_table = resolve_maintainers patches in
97111a47
C
501 (if !found_a_maintainer = false then git_options := !not_linux);
502 (if not (git_args = "") then git_options := !git_options^" "^git_args);
755320b0 503 make_output_files subject cover message maintainer_table file !nomerge