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 | ||
c491d8ee C |
27 | (* split patch per file *) |
28 | ||
29 | (* ------------------------------------------------------------------------ *) | |
30 | (* The following are a reminder of what this information should look like. | |
31 | These values are not used. See the README file for information on how to | |
32 | create a .splitpatch file in your home directory. *) | |
33 | ||
34 | let from = ref "email@xyz.org" | |
35 | let git_tree = ref "/var/linuxes/linux-next" | |
36 | let git_options = ref "--cc=kernel-janitors@vger.kernel.org --suppress-cc=self" | |
97111a47 | 37 | let not_linux = ref "--suppress-cc=self" |
c491d8ee C |
38 | let prefix_before = ref (Some "/var/linuxes/linux-next") |
39 | let prefix_after = ref (Some "/var/julia/linuxcopy") | |
40 | ||
41 | (* ------------------------------------------------------------------------ *) | |
42 | (* misc *) | |
43 | ||
44 | let 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) | |
54 | let cmd_to_list command = | |
55 | let (l,_) = process_output_to_list2 command in l | |
56 | let process_output_to_list = cmd_to_list | |
57 | let cmd_to_list_and_status = process_output_to_list2 | |
58 | ||
59 | let safe_chop_extension s = try Filename.chop_extension s with _ -> s | |
60 | ||
61 | let 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 | ||
69 | let 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 | ||
76 | let 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 | ||
97 | let 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 | ||
137 | let 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 | ||
142 | let 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 | ||
147 | let checkpatch_command file = | |
148 | Printf.sprintf "cd %s; scripts/checkpatch.pl %s" !git_tree file | |
149 | ||
150 | let default_string = "THE REST" (* split by file *) | |
151 | ||
152 | (* ------------------------------------------------------------------------ *) | |
153 | (* ------------------------------------------------------------------------ *) | |
154 | (* Template file processing *) | |
155 | ||
156 | let 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 | ||
172 | let 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 | ||
192 | let spaces = Str.regexp "[ \t]+" | |
193 | ||
194 | let 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 | ||
207 | let 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 | ||
214 | let is_diff = Str.regexp "diff " | |
215 | let 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 |
265 | let uctr = ref 0 |
266 | ||
267 | let found_a_maintainer = ref false | |
268 | ||
c491d8ee C |
269 | let 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 | ||
310 | let 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 | ||
322 | let 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 | ||
336 | let print_all o l = | |
337 | List.iter (function x -> Printf.fprintf o "%s\n" x) l | |
338 | ||
339 | let 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 | ||
352 | let 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 | ||
409 | let 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 | ||
438 | let mail_sender = "git send-email" (* use this when it works *) | |
439 | let mail_sender = "cocci-send-email.perl" | |
440 | ||
441 | let 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 | ||
457 | let 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 | ||
473 | let 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 | ||
482 | let _ = | |
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 |