2 * Copyright 2010, INRIA, University of Copenhagen
3 * Julia Lawall, Rene Rydhof Hansen, Gilles Muller, Nicolas Palix
4 * Copyright 2005-2009, Ecole des Mines de Nantes, University of Copenhagen
5 * Yoann Padioleau, Julia Lawall, Rene Rydhof Hansen, Henrik Stuart, Gilles Muller, Nicolas Palix
6 * This file is part of Coccinelle.
8 * Coccinelle is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, according to version 2 of the License.
12 * Coccinelle is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Coccinelle. If not, see <http://www.gnu.org/licenses/>.
20 * The authors reserve the right to distribute this or future versions of
21 * Coccinelle under other licenses.
26 * Copyright 2010, INRIA, University of Copenhagen
27 * Julia Lawall, Rene Rydhof Hansen, Gilles Muller, Nicolas Palix
28 * Copyright 2005-2009, Ecole des Mines de Nantes, University of Copenhagen
29 * Yoann Padioleau, Julia Lawall, Rene Rydhof Hansen, Henrik Stuart, Gilles Muller, Nicolas Palix
30 * This file is part of Coccinelle.
32 * Coccinelle is free software: you can redistribute it and/or modify
33 * it under the terms of the GNU General Public License as published by
34 * the Free Software Foundation, according to version 2 of the License.
36 * Coccinelle is distributed in the hope that it will be useful,
37 * but WITHOUT ANY WARRANTY; without even the implied warranty of
38 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 * GNU General Public License for more details.
41 * You should have received a copy of the GNU General Public License
42 * along with Coccinelle. If not, see <http://www.gnu.org/licenses/>.
44 * The authors reserve the right to distribute this or future versions of
45 * Coccinelle under other licenses.
49 (* split patch per file *)
51 (* ------------------------------------------------------------------------ *)
52 (* The following are a reminder of what this information should look like.
53 These values are not used. See the README file for information on how to
54 create a .splitpatch file in your home directory. *)
56 let from = ref "email@xyz.org"
57 let git_tree = ref "/var/linuxes/linux-next"
58 let git_options = ref "--cc=kernel-janitors@vger.kernel.org --suppress-cc=self"
59 let prefix_before = ref (Some
"/var/linuxes/linux-next")
60 let prefix_after = ref (Some
"/var/julia/linuxcopy")
62 (* ------------------------------------------------------------------------ *)
65 let process_output_to_list2 = fun command
->
66 let chan = Unix.open_process_in command
in
67 let res = ref ([] : string list
) in
68 let rec process_otl_aux () =
69 let e = input_line
chan in
72 try process_otl_aux ()
74 let stat = Unix.close_process_in
chan in (List.rev
!res,stat)
75 let cmd_to_list command
=
76 let (l
,_
) = process_output_to_list2 command
in l
77 let process_output_to_list = cmd_to_list
78 let cmd_to_list_and_status = process_output_to_list2
80 let safe_chop_extension s
= try Filename.chop_extension s
with _
-> s
82 let safe_get_extension s
=
83 match List.rev
(Str.split
(Str.regexp_string
".") s
) with
84 ext
::_
::rest
-> Some
(String.concat
"." (List.rev rest
))
87 (* ------------------------------------------------------------------------ *)
88 (* set configuration variables *)
90 let from_from_template template
=
92 cmd_to_list (Printf.sprintf
"grep Signed-off-by: %s" template
) in
93 match signed_offs with
94 x
::xs
-> String.concat
" " (Str.split
(Str.regexp
"[ \t]+") x
)
95 | _
-> failwith
"No Signed-off-by in template file"
97 let from_from_gitconfig path
=
98 let config = path^
"/.git/config" in
99 if Sys.file_exists
config
101 let i = open_in
config in
102 let rec inner_loop _
=
103 let l = input_line
i in
104 match Str.split
(Str.regexp
"[ \t]+") l with
105 "from"::"="::f
-> from := String.concat
" " f
107 if String.length
l >= 1 && String.get
l 0 = '
['
110 let rec outer_loop _
=
111 let l = input_line
i in
115 (try outer_loop() with Not_found
-> ());
118 let read_configs template
=
119 let temporary_git_tree = ref None
in
121 prefix_before := None
;
122 prefix_after := None
;
123 (* get information in message template, lowest priority *)
124 from := from_from_template template
;
125 (* get information in git config *)
126 let rec loop = function
129 if Sys.file_exists
".git"
131 begin temporary_git_tree := Some path
; from_from_gitconfig path
end
132 else loop (Filename.dirname path
) in
134 (* get information from .splitpatch *)
135 let home = List.hd
(cmd_to_list "ls -d ~") in
136 let config = home^
"/.splitpatch" in
137 (if Sys.file_exists
config
139 let i = open_in
config in
141 let l = input_line
i in
142 (* bounded split doesn't split at = in value part *)
143 (match Str.bounded_split
(Str.regexp
"[ \t]*=[ \t]*") l 2 with
144 ["from";s
] -> from := s
145 | ["git_tree";s
] -> temporary_git_tree := Some s
146 | ["git_options";s
] -> git_options := s
147 | ["prefix_before";s
] -> prefix_before := Some s
148 | ["prefix_after";s
] -> prefix_after := Some s
149 | _
-> Printf.fprintf stderr
"unknown line: %s\n" l);
151 try loop() with End_of_file
-> close_in
i);
152 match !temporary_git_tree with
153 None
-> failwith
"Unable to find Linux source tree"
154 | Some g
-> git_tree := g
156 (* ------------------------------------------------------------------------ *)
158 let maintainer_command file
=
160 "cd %s; scripts/get_maintainer.pl --separator , --nogit -f %s"
163 let subsystem_command file
=
165 "cd %s; scripts/get_maintainer.pl --nogit --subsystem -f %s | grep -v @"
168 let checkpatch_command file
=
169 Printf.sprintf
"cd %s; scripts/checkpatch.pl %s" !git_tree file
171 let default_string = "THE REST" (* split by file *)
173 (* ------------------------------------------------------------------------ *)
174 (* ------------------------------------------------------------------------ *)
175 (* Template file processing *)
177 let read_up_to_dashes i =
178 let lines = ref [] in
180 let l = input_line
i in
183 else begin lines := l :: !lines; loop() end in
184 (try loop() with End_of_file
-> ());
187 ""::lines -> List.rev
lines (* drop last line if blank *)
188 | lines -> List.rev
lines in
190 ""::lines -> lines (* drop first line if blank *)
193 let get_template_information file
=
194 let i = open_in file
in
196 let subject = read_up_to_dashes i in
199 let cover = read_up_to_dashes i in
200 let message = read_up_to_dashes i in
202 then (subject,None
,cover)
203 else (subject,Some
cover,message)
206 ("Subject must be exactly one line "^
207 (string_of_int
(List.length
subject)))
209 (* ------------------------------------------------------------------------ *)
210 (* ------------------------------------------------------------------------ *)
211 (* Patch processing *)
213 let spaces = Str.regexp
"[ \t]+"
215 let fix_before_after l prefix
= function
217 (match Str.split
spaces l with
218 ("diff"|"+++"|"---")::_
->
219 (match Str.split
(Str.regexp old_prefix
) l with
221 (match Str.split_delim
(Str.regexp
("[ \t]"^prefix
)) a
with
222 [_
;""] -> a^b
(* prefix is already there *)
229 match Str.split
spaces l with
230 (("+++"|"---") as a
)::path
::rest
-> Printf.sprintf
"%s %s" a path
233 (* ------------------------------------------------------------------------ *)
235 let is_diff = Str.regexp
"diff "
237 let patches = ref [] in
240 match Str.split_delim
(Str.regexp
",") l with
241 [_
;size
] -> int_of_string size
243 | _
-> failwith
("bad size: "^
l) in
244 let rec read_diff_or_atat _
=
245 let l = input_line
i in
246 let l = fix_date(fix_before_after l "a" !prefix_before) in
247 let l = fix_date(fix_before_after l "b" !prefix_after) in
248 match Str.split
spaces l with
250 (if List.length
!cur > 0
251 then patches := List.rev
!cur :: !patches);
254 | "@@"::min
::pl
::"@@"::rest
->
255 let msize = get_size min
in
256 let psize = get_size pl
in
258 read_hunk
msize psize
259 | "\\"::_
-> cur := l :: !cur; read_diff_or_atat()
262 "expected diff or @@ (diffstat information should not be present)"
264 let l = input_line
i in
265 let l = fix_date(fix_before_after l "a" !prefix_before) in
266 let l = fix_date(fix_before_after l "b" !prefix_after) in
268 match Str.split
spaces l with
269 "+++"::_
-> read_diff_or_atat()
271 and read_hunk
msize psize =
272 if msize = 0 && psize = 0
273 then read_diff_or_atat()
275 let l = input_line
i in
277 match String.get
l 0 with
278 '
-'
-> read_hunk
(msize - 1) psize
279 | '
+'
-> read_hunk
msize (psize - 1)
280 | _
-> read_hunk
(msize - 1) (psize - 1) in
281 try read_diff_or_atat()
282 with End_of_file
-> List.rev
((List.rev
!cur)::!patches)
284 (* ------------------------------------------------------------------------ *)
286 let resolve_maintainers patches =
287 let maintainer_table = Hashtbl.create
(List.length
patches) in
291 (match Str.split
(Str.regexp
" a/") diff_line
with
293 (match Str.split
spaces after
with
296 List.hd
(cmd_to_list (maintainer_command file
)) in
298 cmd_to_list (subsystem_command file
) in
299 let info = (subsystems,maintainers) in
301 try Hashtbl.find
maintainer_table info
304 Hashtbl.add
maintainer_table info cell;
306 cell := (file
,(diff_line
:: rest
)) :: !cell
307 | _
-> failwith
"filename not found")
309 failwith
(Printf.sprintf
"prefix a/ not found in %s" diff_line
))
310 | _
-> failwith
"bad diff line")
314 (* ------------------------------------------------------------------------ *)
316 let common_prefix l1 l2
=
317 let rec loop = function
318 ([],_
) | (_
,[]) -> []
319 | (x
::xs
,y
::ys
) when x
= y
-> x
:: (loop (xs
,ys
))
321 match loop (l1
,l2
) with
324 (Printf.sprintf
"found nothing in common for %s and %s"
325 (String.concat
"/" l1
) (String.concat
"/" l2
))
328 let merge_files the_rest
= function
331 let butlast l = if the_rest
then l else List.rev
(List.tl
(List.rev
l)) in
332 match List.map
(function s
-> Str.split
(Str.regexp
"/") s
) files
with
334 let rec loop res = function
335 [] -> String.concat
"/" res
336 | x
::rest
-> loop (common_prefix res x
) rest
in
337 loop (butlast first
) rest
338 | _
-> failwith
"not possible"
340 (* ------------------------------------------------------------------------ *)
343 List.iter
(function x
-> Printf.fprintf o
"%s\n" x
) l
345 let make_mail_header o date
maintainers ctr number
subject =
346 Printf.fprintf o
"From nobody %s\n" date
;
347 Printf.fprintf o
"From: %s\n" !from;
348 (match Str.split
(Str.regexp_string
",") maintainers with
349 [x
] -> Printf.fprintf o
"To: %s\n" x
351 Printf.fprintf o
"To: %s\n" x
;
352 Printf.fprintf o
"Cc: %s\n" (String.concat
"," xs
)
353 | _
-> failwith
"no maintainers");
355 then Printf.fprintf o
"Subject: [PATCH] %s\n\n" subject
356 else Printf.fprintf o
"Subject: [PATCH %d/%d] %s\n\n" ctr number
subject
358 let make_message_files subject cover message date
maintainer_table
359 patch front add_ext
=
363 (function (services
,maintainers) ->
366 if services
=[default_string]
368 (* if no maintainer, then one file per diff *)
370 (function (file
,diff
) ->
372 (!ctr,true,maintainers,[file
],[diff
]))
378 let (files
,diffs
) = List.split
(List.rev
!diffs
) in
379 (!ctr,false,maintainers,files
,diffs
)::rest
381 maintainer_table [] in
382 let number = List.length
elements in
385 (function (ctr,the_rest
,maintainers,files
,diffs
) ->
386 let output_file = add_ext
(Printf.sprintf
"%s%d" front
ctr) in
387 let o = open_out
output_file in
388 make_mail_header o date
maintainers ctr number
389 (Printf.sprintf
"%s: %s" (merge_files the_rest files
) subject);
391 Printf.fprintf
o "\n---\n";
392 let (nm
,o1
) = Filename.open_temp_file
"patch" "patch" in
393 List.iter
(print_all o1
) (List.rev diffs
);
397 (Printf.sprintf
"diffstat -p1 < %s ; /bin/rm %s" nm nm
) in
398 List.iter
(print_all o) [diffstat];
399 Printf.fprintf
o "\n";
400 List.iter
(print_all o) diffs
;
401 Printf.fprintf
o "\n";
404 cmd_to_list_and_status
405 (checkpatch_command ((Sys.getcwd
())^
"/"^
output_file)) in
406 (if not
(stat = Unix.WEXITED
0)
407 then (print_all stderr
info; Printf.fprintf stderr
"\n"));
409 (List.rev
elements) in
410 let later = add_ext
(Printf.sprintf
"%s%d" front
(number+1)) in
411 if Sys.file_exists
later
412 then Printf.fprintf stderr
"Warning: %s and other files may be left over from a previous run\n" later;
415 let make_cover_file n
subject cover front date
maintainer_table =
419 let common_maintainers =
420 let intersect l1 l2
=
423 (function i -> function cur ->
424 if List.mem
cur l2
then cur :: i else i)
426 let start = ref true in
429 (function (services
,maintainers) ->
432 let cur = Str.split
(Str.regexp_string
",") maintainers in
434 then begin start := false; cur end
435 else intersect cur rest
)
436 maintainer_table []) in
437 let output_file = Printf.sprintf
"%s.cover" front
in
438 let o = open_out
output_file in
439 make_mail_header o date
common_maintainers 0 n
subject;
441 Printf.fprintf
o "\n";
444 let mail_sender = "git send-email" (* use this when it works *)
445 let mail_sender = "cocci-send-email.perl"
447 let generate_command front
cover generated =
448 let output_file = front^
".cmd" in
449 let o = open_out
output_file in
453 "%s --auto-to --no-thread --from=\"%s\" %s $* %s\n"
454 mail_sender !from !git_options
455 (String.concat
" " generated)
458 "%s --auto-to --thread --from=\"%s\" %s $* %s\n"
459 mail_sender !from !git_options
460 (String.concat
" " ((front^
".cover") :: generated)));
463 let make_output_files subject cover message maintainer_table patch
=
464 let date = List.hd
(cmd_to_list "date") in
465 let front = safe_chop_extension patch
in
467 match safe_get_extension patch
with
468 Some ext
-> (function s
-> s ^
"." ^ ext
)
469 | None
-> (function s
-> s
) in
471 make_message_files subject cover message date maintainer_table
472 patch
front add_ext in
473 make_cover_file (List.length
generated) subject cover front date
475 generate_command front cover generated
477 (* ------------------------------------------------------------------------ *)
480 let (other_args
,files
) =
482 (function a
-> String.length a
> 1 && String.get a
0 = '
-'
)
485 [file
] -> (file
,String.concat
" " other_args
)
486 | _
-> failwith
"Only one file allowed"
489 let (file
,git_args
) = parse_args (List.tl
(Array.to_list
Sys.argv
)) in
490 let message_file = (safe_chop_extension file
)^
".msg" in
491 (* set up environment *)
492 read_configs message_file;
493 (if not
(git_args
= "") then git_options := !git_options^
" "^git_args
);
494 (* get message information *)
495 let (subject,cover,message) = get_template_information message_file in
497 let i = open_in file
in
498 let patches = split_patch i in
500 let maintainer_table = resolve_maintainers patches in
501 make_output_files subject cover message maintainer_table file