Release coccinelle-0.2.3rc1
[bpt/coccinelle.git] / tools / split_patch.ml
dissimilarity index 90%
index f6e0f73..9b058c5 100644 (file)
-(*
- * Copyright 2005-2009, Ecole des Mines de Nantes, University of Copenhagen
- * Yoann Padioleau, Julia Lawall, Rene Rydhof Hansen, Henrik Stuart, Gilles Muller, Nicolas Palix
- * This file is part of Coccinelle.
- *
- * Coccinelle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, according to version 2 of the License.
- *
- * Coccinelle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Coccinelle.  If not, see <http://www.gnu.org/licenses/>.
- *
- * The authors reserve the right to distribute this or future versions of
- * Coccinelle under other licenses.
- *)
-
-
-open Common
-
-module CP = Classic_patch 
-
-(* ./split_patch ../demos/janitorings/patch-kzalloc-vnew3.patch /tmp/xx "0 -> NULL" ../bodymail.doc  
-./split_patch /tmp/badzero.patch /tmp/xx ../mailbody.doc ../kernel_dirs.meta
-
-update: see  http://lwn.net/Articles/284469/
-for a script using git annotate to find automatically to who send
-a patch (by looking at authors of lines close concerning the patch I guess
-*)
-
-(*****************************************************************************)
-(* Flags *)
-(*****************************************************************************)
-
-let just_patch = ref false
-let verbose = ref true
-
-let pr2 s = 
-  if !verbose then pr2 s
-
-(*****************************************************************************)
-(* Helpers *)
-(*****************************************************************************)
-
-let print_header_patch patch = 
-  patch +> List.iter (function CP.File (s, header, body) -> pr s)
-
-
-(*****************************************************************************)
-(* Grouping strategies *)
-(*****************************************************************************)
-
-let group_patch_depth_2 patch = 
-  let patch_with_dir = patch +> List.map (function (CP.File (s,header,body)) ->
-    Common.split "/" (Common.dirname s), 
-    (CP.File (s, header, body))
-  )
-  in
-  let rec aux_patch xs = 
-    match xs with
-    | [] -> []
-    | (dir_elems,x)::xs -> 
-        let cond, base = 
-          if List.length dir_elems >= 2 then
-            let base = Common.take 2 dir_elems in
-            (fun dir_elems' -> 
-              List.length dir_elems' >= 2 && Common.take 2 dir_elems' = base),
-            base
-          else 
-            (fun dir_elems' -> dir_elems' = dir_elems), 
-            dir_elems
-        in
-        
-        let (yes, no) = xs +> Common.partition_either (fun (dir_elems', x) ->
-          if cond dir_elems'
-          then Left x
-          else Right (dir_elems', x)
-        ) in
-        (Common.join "/" base, ["NOEMAIL"], (x::yes))::aux_patch no
-  in
-  aux_patch patch_with_dir
-
-
-
-let group_patch_subsystem_info patch subinfo = 
-  let patch_with_dir = patch +> List.map (function (CP.File (s,header,body)) ->
-    (Common.dirname s),     (CP.File (s, header, body))
-  )
-  in
-  let index = Maintainers.mk_inverted_index_subsystem subinfo in
-  let hash = Maintainers.subsystem_to_hash subinfo in
-
-  let rec aux_patch xs = 
-    match xs with
-    | [] -> []
-    | ((dir,patchitem)::xs) as xs' -> 
-        let leader = 
-          try Hashtbl.find index dir 
-          with Not_found -> failwith ("cant find leader for : " ^ dir) 
-        in
-        let (emailsleader, subdirs) = 
-          try Hashtbl.find hash leader
-          with Not_found -> failwith ("cant find subdirs of : " ^ leader) 
-        in
-
-        (match emailsleader with
-        | ["NOEMAIL"] | [] | [""] -> pr2 ("no leader maintainer for: "^leader);
-        | _ -> ()
-        );
-
-        let emails = ref emailsleader in
-        let allsubdirs = (leader, emailsleader)::subdirs in
-
-        let (yes, no) = xs' +> Common.partition_either (fun (dir',patchitem')->
-          try (
-            let emailsdir' = List.assoc dir' allsubdirs in
-            emails := !emails $+$ emailsdir';
-            Left patchitem'
-          ) with Not_found -> Right (dir', patchitem')
-         (*
-          if List.mem dir' (leader::subdirs)
-          then Left x
-          else Right (dir', x)
-         *)
-        ) in
-        (leader, !emails, yes)::aux_patch no
-  in
-  aux_patch patch_with_dir
-
-
-(*****************************************************************************)
-(* Split patch *)
-(*****************************************************************************)
-let i_to_s_padded i total = 
-  match i with
-  | i when i < 10 && total >= 10 -> "0" ^ i_to_s i
-  | i when i < 100 -> i_to_s i
-  | i -> i_to_s i
-
-let split_patch file prefix bodymail subinfofile = 
-  let patch = CP.parse_patch file in
-
-  let subsystem_info = Maintainers.parse_subsystem_info subinfofile in
-  let minipatches = group_patch_subsystem_info patch subsystem_info in
-  (* let minipatches = group_patch_depth_2 patch in *)
-
-  let total = List.length minipatches in
-  let minipatches_indexed = Common.index_list_1 minipatches in
-
-  let (subject, bodymail_rest) = 
-    match Common.cat bodymail with
-    | x::y::xs -> 
-        if x =~ "Subject: \\(.*\\)" 
-        then
-          let subject = matched1 x in
-          if y =~ "[-]+$" 
-          then
-            subject, xs
-          else failwith ("wrong format for mailbody in:" ^ bodymail)
-        else failwith ("wrong format for mailbody in:" ^ bodymail)
-    | _ -> failwith ("wrong format for mailbody in:" ^ bodymail)
-  in
-
-  Common.command2_y_or_no ("rm -f " ^ prefix ^ "*");
-  
-
-  minipatches_indexed +> List.iter (fun ((dir,emails, minipatch), i) -> 
-    let numpatch = i_to_s_padded  i total in
-    let tmpfile = prefix ^  numpatch ^ ".mail" in
-    let patchfile = "/tmp/x.patch" in
-    pr2 ("generating :" ^ tmpfile ^ " for " ^ dir);
-
-    CP.unparse_patch minipatch patchfile;
-
-    let emails = 
-      (match emails with
-      | ["NOEMAIL"] | [] | [""] -> 
-          pr2 "no maintainer"; []
-      | xs -> xs
-      ) @ ["akpm@linux-foundation.org"]
-    in
-
-
-    if !just_patch
-    then command2(sprintf "cat %s > %s" patchfile tmpfile)
-    else begin
-      Common.with_open_outfile tmpfile (fun (pr_no_nl, chan) -> 
-        let pr s = pr_no_nl (s ^ "\n") in
-        pr "To: kernel-janitors@vger.kernel.org";
-        pr (sprintf "Subject: [PATCH %s/%d] %s, for %s" 
-               numpatch total subject dir);
-        pr ("Cc: " ^ (Common.join ", " (emails @ ["linux-kernel@vger.kernel.org"])));
-        pr "BCC: padator@wanadoo.fr";
-        pr "From: Yoann Padioleau <padator@wanadoo.fr>";
-        pr "--text follows this line--";
-        
-        pr "";
-        bodymail_rest +> List.iter pr;
-        pr "";
-        pr "Signed-off-by: Yoann Padioleau <padator@wanadoo.fr>";
-        emails +> List.iter (fun s -> 
-          pr ("Cc: " ^ s)
-        );
-        pr "---";
-
-        pr "";
-      );
-
-      command2(sprintf "diffstat -p1 %s >> %s" patchfile tmpfile);
-      command2(sprintf "echo >> %s" tmpfile);
-      command2(sprintf "cat %s >> %s" patchfile tmpfile);
-    end
-  )
-
-  
-
-(*****************************************************************************)
-(* Test *)
-(*****************************************************************************)
-
-let test_patch file = 
-  let patch = CP.parse_patch file in
-  let groups = group_patch_depth_2 patch in
-  groups +> List.iter (fun (dir, email, minipatch) -> 
-    print_header_patch minipatch;
-    pr ""
-  )
-
-
-(*****************************************************************************)
-(* Main entry point *)
-(*****************************************************************************)
-
-let main () = 
-  begin
-    let args = ref [] in
-    let options = [
-      "-just_patch", Arg.Set just_patch, "";
-      "-no_verbose", Arg.Clear verbose, "";
-    ] in
-    let usage_msg = 
-      "Usage: " ^ basename Sys.argv.(0) ^ 
-        " <patch> <prefix> <bodymailfile> <maintainerfile> [options]" ^ "\n" 
-      ^ "Options are:"
-    in
-
-    Arg.parse (Arg.align options) (fun x -> args := x::!args) usage_msg;
-    args := List.rev !args;
-
-    (match (!args) with
-    | [patch] -> test_patch patch
-    | [patch;prefix;bodymail;subinfofile] -> 
-        split_patch patch prefix bodymail subinfofile;
-
-        command2("rm -f /tmp/split_check*");
-        let checkfile = "/tmp/split_check.all" in 
-        let checkprefix = "/tmp/split_check-xx" in
-        save_excursion verbose (fun () -> 
-        save_excursion just_patch (fun () -> 
-          just_patch := true;
-          verbose := false;
-          split_patch patch checkprefix bodymail subinfofile;
-        ));
-        command2("cat /tmp/split_check*.mail > " ^ checkfile);
-
-        let diff = Common.cmd_to_list (sprintf "diff %s %s " patch checkfile) 
-        in
-        let samesize = Common.filesize patch = Common.filesize checkfile in
-        if (List.length diff <> 0)
-        then
-          if samesize 
-          then pr2 "diff but at least same size"
-          else pr2 "PB: diff and not same size"
-        
-    | _ -> Arg.usage (Arg.align options) usage_msg; 
-    )
-  end
-
-(*****************************************************************************)
-let _ =
-  main ()
-
+(*
+ * Copyright 2005-2010, Ecole des Mines de Nantes, University of Copenhagen
+ * Yoann Padioleau, Julia Lawall, Rene Rydhof Hansen, Henrik Stuart, Gilles Muller, Nicolas Palix
+ * This file is part of Coccinelle.
+ *
+ * Coccinelle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, according to version 2 of the License.
+ *
+ * Coccinelle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Coccinelle.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * The authors reserve the right to distribute this or future versions of
+ * Coccinelle under other licenses.
+ *)
+
+
+(*
+ * Copyright 2005-2010, Ecole des Mines de Nantes, University of Copenhagen
+ * Yoann Padioleau, Julia Lawall, Rene Rydhof Hansen, Henrik Stuart, Gilles Muller, Nicolas Palix
+ * This file is part of Coccinelle.
+ *
+ * Coccinelle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, according to version 2 of the License.
+ *
+ * Coccinelle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Coccinelle.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * The authors reserve the right to distribute this or future versions of
+ * Coccinelle under other licenses.
+ *)
+
+
+(* split patch per file *)
+
+let git_tree = "/var/linuxes/linux-next"
+
+let maintainer_command file =
+  Printf.sprintf
+    "cd %s; scripts/get_maintainer.pl --separator , --nomultiline --nogit -f %s"
+    git_tree file
+
+let subsystem_command file =
+  Printf.sprintf
+    "cd %s; scripts/get_maintainer.pl --nogit --subsystem -f %s | grep -v @"
+    git_tree file
+
+let checkpatch_command file =
+  Printf.sprintf "cd %s; scripts/checkpatch.pl %s" git_tree file
+
+let default_string = "THE REST" (* split by file *)
+
+let extra_cc = Some "kernel-janitors@vger.kernel.org" (* comma separated *)
+
+let prefix_before = Some "/var/linuxes/linux-next"
+let prefix_after = Some "/var/julia/linuxcopy"
+
+(* ------------------------------------------------------------------------ *)
+
+let spaces = Str.regexp "[ \t]"
+
+let fix_before_after l prefix = function
+    Some old_prefix ->
+      (match Str.split spaces l with
+       ("diff"|"+++"|"---")::_ ->
+         (match Str.split (Str.regexp old_prefix) l with
+           [a;b] ->
+             (match Str.split_delim (Str.regexp ("[ \t]"^prefix)) a with
+               [_;""] -> a^b (* prefix is alwaredy there *)
+             | _ -> a^prefix^b)
+         | _ -> l)
+      |        _ -> l)
+  | _ -> l
+
+let fix_date l =
+  match Str.split spaces l with
+    (("+++"|"---") as a)::path::rest -> Printf.sprintf "%s %s" a path
+  | _ -> l
+
+(* ------------------------------------------------------------------------ *)
+
+let is_diff = Str.regexp "diff "
+let split_patch i =
+  let patches = ref [] in
+  let cur = ref [] in
+  let get_size l =
+    match Str.split_delim (Str.regexp ",") l with
+      [_;size] -> int_of_string size
+    | _ -> failwith ("bad size: "^l) in
+  let rec read_diff_or_atat _ =
+    let l = input_line i in
+    let l = fix_date(fix_before_after l "a" prefix_before) in
+    let l = fix_date(fix_before_after l "b" prefix_after) in
+    match Str.split spaces l with
+      "diff"::_ ->
+       (if List.length !cur > 0
+       then patches := List.rev !cur :: !patches);
+       cur := [l];
+       read_diff()
+    | "@@"::min::pl::"@@"::rest ->
+       let msize = get_size min in
+       let psize = get_size pl in
+       cur := l :: !cur;
+       read_hunk msize psize
+    | "\\"::_ -> cur := l :: !cur; read_diff_or_atat()
+    | _ ->
+       failwith
+         "expected diff or @@ (diffstat information should not be present)"
+  and read_diff _ =
+    let l = input_line i in
+    let l = fix_date(fix_before_after l "a" prefix_before) in
+    let l = fix_date(fix_before_after l "b" prefix_after) in
+    cur := l :: !cur;
+    match Str.split spaces l with
+      "+++"::_ -> read_diff_or_atat()
+    | _ -> read_diff()
+  and read_hunk msize psize =
+    if msize = 0 && psize = 0
+    then read_diff_or_atat()
+    else
+      let l = input_line i in
+      cur := l :: !cur;
+      match Str.split spaces l with
+       "-"::_ -> read_hunk (msize - 1) psize
+      |        "+"::_ -> read_hunk msize (psize - 1)
+      |        _ -> read_hunk (msize - 1) (psize - 1) in
+  try read_diff_or_atat()
+  with End_of_file -> List.rev ((List.rev !cur)::!patches)
+
+(* ------------------------------------------------------------------------ *)
+
+(* can get_maintainers take a file as an argument, or only a patch? *)
+let resolve_maintainers patches =
+  let maintainer_table = Hashtbl.create (List.length patches) in
+  List.iter
+    (function
+       diff_line::rest ->
+         (match Str.split (Str.regexp " a/") diff_line with
+           [before;after] ->
+             (match Str.split spaces after with
+               file::_ ->
+                 let maintainers =
+                   List.hd (Common.cmd_to_list (maintainer_command file)) in
+                 let maintainers =
+                   match extra_cc with
+                     None -> maintainers
+                   | Some extra_cc -> maintainers ^ "," ^ extra_cc in
+                 let subsystems =
+                   Common.cmd_to_list (subsystem_command file) in
+                 let info = (subsystems,maintainers) in
+                 let cell =
+                   try Hashtbl.find maintainer_table info
+                   with Not_found ->
+                     let cell = ref [] in
+                     Hashtbl.add maintainer_table info cell;
+                     cell in
+                 cell := (file,(diff_line :: rest)) :: !cell
+             | _ -> failwith "filename not found")
+         | _ ->
+             failwith (Printf.sprintf "prefix a/ not found in %s" diff_line))
+      |        _ -> failwith "bad diff line")
+    patches;
+  maintainer_table
+
+(* ------------------------------------------------------------------------ *)
+
+let common_prefix l1 l2 =
+  let rec loop = function
+      ([],_) | (_,[]) -> []
+    | (x::xs,y::ys) when x = y -> x :: (loop (xs,ys))
+    | _ -> [] in
+  match loop (l1,l2) with
+    [] ->
+      failwith
+       (Printf.sprintf "found nothing in common for %s and %s"
+          (String.concat "/" l1) (String.concat "/" l2))
+  | res -> res
+
+let merge_files the_rest files =
+  let butlast l = if the_rest then l else List.rev(List.tl(List.rev l)) in
+  match List.map (function s -> Str.split (Str.regexp "/") s) files with
+    first::rest ->
+      let rec loop res = function
+         [] -> String.concat "/" res
+       | x::rest -> loop (common_prefix res x) rest in
+      loop (butlast first) rest
+  | _ -> failwith "not possible"
+
+(* ------------------------------------------------------------------------ *)
+
+let print_all o l =
+  List.iter (function x -> Printf.fprintf o "%s\n" x) l
+
+let make_output_files template maintainer_table patch =
+  let ctr = ref 0 in
+  let elements =
+    Hashtbl.fold
+      (function (services,maintainers) ->
+       function diffs ->
+         function rest ->
+           if services=[default_string]
+           then
+             (* if no maintainer, then one file per diff *)
+             (List.map
+                (function (file,diff) ->
+                  ctr := !ctr + 1;
+                  (!ctr,true,maintainers,[file],[diff]))
+                (List.rev !diffs)) @
+             rest
+           else
+             begin
+               ctr := !ctr + 1;
+               let (files,diffs) = List.split (List.rev !diffs) in
+               (!ctr,false,maintainers,files,diffs)::rest
+             end)
+      maintainer_table [] in
+  let number = List.length elements in
+  List.iter
+    (function (ctr,the_rest,maintainers,files,diffs) ->
+      let output_file = Printf.sprintf "%s%d" patch ctr in
+      let o = open_out output_file in
+      Printf.fprintf o "To: %s\n\n" maintainers;
+      Printf.fprintf o "Subject: [PATCH %d/%d] %s: "
+       ctr number (merge_files the_rest files);
+      print_all o template;
+      let (nm,o1) = Filename.open_temp_file "patch" "patch" in
+      List.iter (print_all o1) (List.rev diffs);
+      close_out o1;
+      let diffstat =
+       Common.cmd_to_list
+         (Printf.sprintf "diffstat -p1 < %s ; /bin/rm %s" nm nm) in
+      List.iter (print_all o) [diffstat];
+      Printf.fprintf o "\n";
+      List.iter (print_all o) diffs;
+      close_out o;
+      let (info,stat) =
+       Common.cmd_to_list_and_status
+         (checkpatch_command ((Sys.getcwd())^"/"^output_file)) in
+      if not(stat = Unix.WEXITED 0)
+      then (print_all stderr info; Printf.fprintf stderr "\n"))
+    (List.rev elements);
+  let later = Printf.sprintf "%s%d" patch (number + 1) in
+  if Sys.file_exists later
+  then Printf.fprintf stderr "Warning: %s and other files may be left over from a previous run\n" later
+    
+(* ------------------------------------------------------------------------ *)
+
+let file = ref ""
+
+let options = []
+
+let usage = ""
+
+let anonymous x = file := x
+
+let _ =
+  Arg.parse (Arg.align options) (fun x -> file := x) usage;
+  let i = open_in !file in
+  let patches = split_patch i in
+  close_in i;
+  let maintainer_table = resolve_maintainers patches in
+  let template = Common.cmd_to_list (Printf.sprintf "cat %s.tmp" !file) in
+  make_output_files template maintainer_table !file