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