Commit | Line | Data |
---|---|---|
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. | |
32 | These values are not used. See the README file for information on how to | |
33 | create a .splitpatch file in your home directory. *) | |
34 | ||
35 | let from = ref "email@xyz.org" | |
36 | let git_tree = ref "/var/linuxes/linux-next" | |
37 | let git_options = ref "--cc=kernel-janitors@vger.kernel.org --suppress-cc=self" | |
97111a47 | 38 | let not_linux = ref "--suppress-cc=self" |
c491d8ee C |
39 | let prefix_before = ref (Some "/var/linuxes/linux-next") |
40 | let prefix_after = ref (Some "/var/julia/linuxcopy") | |
41 | ||
42 | (* ------------------------------------------------------------------------ *) | |
43 | (* misc *) | |
44 | ||
45 | let 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) | |
55 | let cmd_to_list command = | |
56 | let (l,_) = process_output_to_list2 command in l | |
57 | let process_output_to_list = cmd_to_list | |
58 | let cmd_to_list_and_status = process_output_to_list2 | |
59 | ||
60 | let safe_chop_extension s = try Filename.chop_extension s with _ -> s | |
61 | ||
62 | let 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 | ||
70 | let 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 | ||
77 | let 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 | ||
98 | let 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 | ||
138 | let 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 | ||
143 | let 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 | ||
148 | let checkpatch_command file = | |
149 | Printf.sprintf "cd %s; scripts/checkpatch.pl %s" !git_tree file | |
150 | ||
151 | let default_string = "THE REST" (* split by file *) | |
152 | ||
153 | (* ------------------------------------------------------------------------ *) | |
154 | (* ------------------------------------------------------------------------ *) | |
155 | (* Template file processing *) | |
156 | ||
157 | let 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 | ||
173 | let 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 | ||
193 | let spaces = Str.regexp "[ \t]+" | |
194 | ||
195 | let 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 | ||
208 | let 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 | ||
215 | let is_diff = Str.regexp "diff " | |
216 | let 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 |
266 | let uctr = ref 0 |
267 | ||
268 | let found_a_maintainer = ref false | |
269 | ||
755320b0 C |
270 | let 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 | ||
279 | let 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 |
292 | let 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 |
334 | let print_all o l = |
335 | List.iter (function x -> Printf.fprintf o "%s\n" x) l | |
336 | ||
337 | let 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 | ||
350 | let 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 | ||
412 | let 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 | ||
441 | let mail_sender = "git send-email" (* use this when it works *) | |
442 | let mail_sender = "cocci-send-email.perl" | |
443 | ||
444 | let 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 | 460 | let 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 |
476 | let nomerge = ref false |
477 | ||
c491d8ee C |
478 | let 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 | ||
489 | let _ = | |
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 |