123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467(* Yoann Padioleau
*
* Copyright (C) 2006 Yoann Padioleau
* Copyright (C) 2010 Facebook
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation, with the
* special exception on linking described in file license.txt.
*
* This library 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 file
* license.txt for more details.
*)openCommon(*****************************************************************************)(* Prelude *)(*****************************************************************************)(*
* history:
* Was first written for coccinelle, for patchparse experiment.
*
*
* note: diff can have some ambuiguities ? ex:
* file1:
*
* -- /home/pad/week-end/cocci-c/txt/file1 2006-02-16 11:32:00.000000000 +0100
*
* file2:
*
* -- titi
*
* => Diff:
*
* diff -u -p -b -B -r /home/pad/week-end/cocci-c/txt/file1 /home/pad/week-end/cocci-c/txt/file2
* --- /home/pad/week-end/cocci-c/txt/file1 2006-02-16 11:32:38.000000000 +0100
* +++ /home/pad/week-end/cocci-c/txt/file2 2006-02-16 11:32:07.000000000 +0100
* @@ -1 +1 @@
* --- /home/pad/week-end/cocci-c/txt/file1 2006-02-16 11:32:00.000000000 +0100
* +-- titi
* \ No newline at end of file
*
* ambiguity ? no because there is a preceding diff line command
*
*
*
* todo:
* in fact fragile, cf txt/diff_format.txt,
* "^--- " is not enough for a regexp, there must be just before a diff command
* todo?: does it handle when a file was just created/deleted ?
* --- a/drivers/acorn/char/defkeymap-acorn.c_shipped Sat Jun 14 12:18:56 2003
* +++ /dev/null Wed Dec 31 16:00:00 1969
* does it need to ? (I think no, after all, if deleted or created, there is no
* interesting patch info to print)
*
* in main.ml:
*
* let classic_patch_file = ref ""
* "-classic_patch_file", Arg.Set_string classic_patch_file,
* " the patch file corresponding to the linux version we are analyzing" ;
* ] in
*
* | "test_parse_classic_patch", [] ->
* Classic_patch.parse_patch (cat "/tmp/patch1") +> ignore
*
* | "test_filter_driver", [] ->
* cat "/home/pad/kernels/patches/patch-2.5.71"
* +> Classic_patch.filter_driver_sound
* +> List.iter pr2
*
*)(*****************************************************************************)(* Types *)(*****************************************************************************)typepatch_raw=stringlist(* parsing patch related *)typepatchinfo=(filename,fileinfo)Common.assocandfileinfo=((int*int)*hunk)list(* inv: the regions are sorted, because we process patch from start to
* end of file *)andhunk=stringlist(* use parse_hunk() to get patchline *)(* with tarzan *)(* the strings are without the mark *)typepatchline=|Contextofstring|Minusofstring|Plusofstringlet_mark_regexp="^[-+ ]"letregexp_mark_and_line="^\\([-+ ]\\)\\(.*\\)"letstr_regexp_no_mark="^[-+ ]\\(.*\\)"typestat={mutablenb_minus:int;mutablenb_plus:int}letverbose=false(* generating patch related *)typeedition_cmd=|RemoveLinesofintlist|PreAddAtofint*stringlist|PostAddAtofint*stringlist(*****************************************************************************)(* Helpers *)(*****************************************************************************)letremove_prefix_marks=ifs=~str_regexp_no_markthenmatched1selsebeginpr2("no mark in: "^s);sendletparse_hunkxs=xs|>List.map(funs->ifs=~regexp_mark_and_linethenlet(mark,rest_line)=Common.matched2sin(matchmarkwith|" "->Contextrest_line|"-"->Minusrest_line|"+"->Plusrest_line|_->raiseImpossible)elsefailwith("wrong format for a hunk: "^s))letrecmark_file_boundary_and_normalize_with_xxxxs=matchxswith|[]->[]|x::xs->ifx=~"^diff .*"thenlethas_minus_and_plus=matchxswith|y::z::_xs->y=~"--- \\(.*\\)"&&z=~"\\+\\+\\+ \\(.*\\)"|_->falseinifhas_minus_and_plusthen(matchxswith|y::z::xs->(* ugly *)let_=y=~"--- \\(.*\\)"inlets_y=matched1yinlet_=z=~"\\+\\+\\+ \\(.*\\)"inlets_z=matched1zinifs_y="/dev/null"then("xxx "^s_z)::mark_file_boundary_and_normalize_with_xxxxselse("xxx "^s_y)::mark_file_boundary_and_normalize_with_xxxxs|_->raiseImpossible)elsebeginmatchxswith|x::xswhenx=~"Binary files .*"->mark_file_boundary_and_normalize_with_xxxxs|_->ifverbosethenpr2("weird, diff header but no modif: "^x);mark_file_boundary_and_normalize_with_xxx(xs)endelsex::mark_file_boundary_and_normalize_with_xxxxs(*****************************************************************************)(* Main entry point *)(*****************************************************************************)let(parse_patch:(stringlist)->patchinfo)=funlines->(* dont need those lines any more *)(* old:
* let lines = lines +> List.filter (fun s ->
* not (s =~ "^\\-\\-\\-" || s =~ "^diff"))
* in
* but this does not handle well patches that remove or mv files which
* will contain some +++ /dev/null or ---/dev/null so just normalize it
*)letlines=mark_file_boundary_and_normalize_with_xxxlinesin(* note: split_list_regexp can generate __noheading__ category
* (cf common.ml code)
*)letdouble_splitted=lines|>Common2.split_list_regexp"^xxx "|>List.map(fun(s,group)->(s,Common2.split_list_regexp"^@@"group))indouble_splitted|>List.map(fun(s,group)->ifs=~"^xxx [^/]+/\\([^ \t]+\\)[ \t]?"then(* old:
s =~ "^\\-\\-\\- [^/]+/\\([^/]+\\)/\\(.*\\)/\\([^ \t]+\\)[ \t]"
+> (fun b -> if not b then (pr2 s; pr2 (dump group); assert b));
let (driver_or_sound, subdirs, filename) = matched3 s in
assert drivers|sound
driver_or_sound ^ "/" ^ subdirs ^ "/" ^ filename,
*)let(file)=matched1sinfile,group|>List.map(fun(s,group)->match()with|_whens=~"^@@ \\-\\([0-9]+\\),\\([0-9]+\\) \\+\\([0-9]+\\),\\([0-9]+\\) @@"->let(start1,plus1,_start2,_plus2)=matched4sinlet(start1,plus1)=Common2.pairs_to_i(start1,plus1)in((start1,start1+plus1),(* just put the +- into the string ? *)group)(* [("@@ -1 +0,0 @@", ["-"])] *)|_whens=~"^@@ \\-\\([0-9]+\\) \\+\\([0-9]+\\),\\([0-9]+\\) @@"->let(start1,_start2,_plus2)=matched3sinlet(start1,plus1)=Common2.pairs_to_i(start1,"0")in((start1,start1+plus1),(* just put the +- into the string ? *)group)(* @@ -0,0 +1 @@ *)|_whens=~"^@@ \\-\\([0-9]+\\),\\([0-9]+\\) \\+\\([0-9]+\\) @@"->let(start1,plus1,_start2)=matched3sinlet(start1,plus1)=Common2.pairs_to_i(start1,plus1)in((start1,start1+plus1),(* just put the +- into the string ? *)group)(* @@ -1 +1 @@ *)|_whens=~"^@@ \\-\\([0-9]+\\) \\+\\([0-9]+\\) @@"->let(start1,_start2)=matched2sinlet(start1)=s_to_istart1in((start1,start1),(* just put the +- into the string ? *)group)|_->failwith("pb with hunk: "^s))elsefailwith("pb with line: "^s))(*****************************************************************************)(* Hunk processing *)(*****************************************************************************)lethunk_containing_strings(pinfos:patchinfo)=pinfos|>Common.find_some(fun(_file,fileinfo)->Common2.optionise(fun()->fileinfo|>Common.find_some(fun(_limits,hunk)->lethunk'=hunk|>List.mapremove_prefix_markinifList.memshunk'thenSomehunkelseNone)))lethunks_containing_strings(pinfos:patchinfo)=pinfos|>Common.map_filter(fun(_file,fileinfo)->letres=(fileinfo|>Common.map_filter(fun(_limits,hunk)->lethunk'=hunk|>List.mapremove_prefix_markinifList.memshunk'thenSomehunkelseNone))inifnullresthenNoneelseSomeres)|>List.flattenletmodified_linesfileinfo=fileinfo|>List.map(fun((start,_end),hunk)->lethunk=parse_hunkhunkinletnoplus=hunk|>Common.exclude(functionPlus_->true|_->false)inCommon.index_listnoplus|>Common.map_filter(function|Minus_,idx->Some(idx+start)|_->None))|>List.flatten(*****************************************************************************)(* Extra func *)(*****************************************************************************)letdiffstat_filefinfo=letplus=ref0inletminus=ref0infinfo|>List.iter(fun(_,hunk)->letplines=parse_hunkhunkinplines|>List.iter(function|Context_->()|Minus_->incrminus|Plus_->incrplus););{nb_minus=!minus;nb_plus=!plus;}letdiffstatpinfo=pinfo|>List.map(fun(file,fileinfo)->file,diffstat_filefileinfo)letstring_of_statstat=spf"nb plus = %d, nb minus = %d"stat.nb_plusstat.nb_minus(*****************************************************************************)(* misc *)(*****************************************************************************)let(relevant_part:(filename*(int*int))->patchinfo->string)=fun(_filename,(_startl,_endl))_patchinfo->raiseTodo(*
try
let xs = patchinfo#find filename in
let is_in i (min, max) = i >= min && i <= max in
xs +> map_filter (fun ((i,j), s) ->
if ((is_in i (startl, endl)) ||
(is_in j (startl, endl)) ||
(i < startl && j > endl))
then Some s
else None
)
+> String.concat "\nOTHER REGION\n"
with Not_found -> ("NO MODIF in patch file for:" ^ filename)
*)let(_filter_driver_sound:stringlist->stringlist)=fun_lines->raiseTodo(*
let res = ref [] in
let state = ref 0 in
begin
lines +> List.iter (fun s ->
if s =~ "^\\-\\-\\- .*" then state := 0;
(* GET ALSO Documentation, include/*(except asm- ? futur=fs, net *)
(* could filter .h too, newfile *)
if s =~ "^\\-\\-\\- [^/]+/\\([^/]+\\)/\\(.*\\)/\\([a-zA-Z0-9_\\-]+\\.[a-zA-Z0-9_]+\\)[ \t]"
then
let (driver_or_sound, subdirs, filename) = matched3 s in
if driver_or_sound = "drivers" || driver_or_sound = "sound"
then begin state := 1; push2 s res end
else state := 0
else
if s =~ "^\\-\\-\\- .*" then state := 0
else
if !state = 1 then push2 s res
else ()
);
List.rev !res
end
*)(*****************************************************************************)(* Patch generation *)(*****************************************************************************)(*
* This is sligtly complicated. Modifying files is not that easy.
* A far easier path is to use Emacs and do macros. In most cases it
* is easier than programming the modif in OCaml. I don't have
* enough helper functions for such things. Nevertheless it is not
* always practical to run emacs macros from a script so this is a crude
* but possible alternative.
*
* Could use 'ed' as sgimm suggested, or make a small OCaml API around 'ed'.
*)let(generate_patch:edition_cmdlist->filename_in_project:string->Common.filename->stringlist)=funedition_cmds~filename_in_projectfilename->letindexed_lines=Common.catfilename|>Common.index_list_1inletindexed_lines=edition_cmds|>List.fold_left(funindexed_linesedition_cmd->matchedition_cmdwith|RemoveLinesindex_lines->indexed_lines|>Common.exclude(fun(_line,idx)->List.memidxindex_lines)|PreAddAt(lineno,lines_to_add)|PostAddAt(lineno,lines_to_add)->letlines_to_add_fake_indexed=lines_to_add|>List.map(funs->s,-1)inindexed_lines|>Common2.map_flatten(fun(line,idx)->ifidx=linenothen(matchedition_cmdwith|PreAddAt_->lines_to_add_fake_indexed@[line,idx]|PostAddAt_->[line,idx]@lines_to_add_fake_indexed|_->raiseImpossible)else[line,idx]))indexed_linesinletlines'=indexed_lines|>List.mapfstin(* generating diff *)lettmpfile=Common.new_temp_file"genpatch"".patch"inwrite_file~file:tmpfile(Common2.unlineslines');(* pr2 filename_in_project; *)let(xs,_)=Common2.cmd_to_list_and_status(spf"diff -u -p \"%s\" \"%s\""filenametmpfile)inletxs'=xs|>List.map(funs->match()with|_whens=~"^\\-\\-\\- \\([^ \t]+\\)\\([ \t]?\\)"->let(_,rest)=matched2sin"--- a/"^filename_in_project^rest|_whens=~"^\\+\\+\\+ \\([^ \t]+\\)\\([ \t]?\\)"->let(_,rest)=matched2sin"+++ b/"^filename_in_project^rest|_->s)inxs'(*****************************************************************************)(* Debug *)(*****************************************************************************)