diff --git a/patch.opam b/patch.opam index c385177..ae47db4 100644 --- a/patch.opam +++ b/patch.opam @@ -3,7 +3,7 @@ version: "3.0.0~alpha1" maintainer: "Hannes Mehnert " authors: [ "Hannes Mehnert " - "Kate " + "Kate " ] homepage: "https://github.com/hannesm/patch" doc: "https://hannesm.github.io/patch/" diff --git a/src/fname.ml b/src/fname.ml index 99b2c83..8ae4342 100644 --- a/src/fname.ml +++ b/src/fname.ml @@ -97,27 +97,54 @@ let parse s = Ok (Some filename) | Error _ as err -> err -let parse_git_header s = - let parse s = - match parse_filename ~allow_space:true s with - | Ok (s, "") -> Ok (Lib.String.cut '/' s) - | Ok _ -> Error "Unexpected character after closing double-quote in header" - | Error _ as err -> err - in - let rec loop s len i = - if i < len then - match s.[i] with +let parse_git_filename s = + match parse_filename ~allow_space:true s with + | Ok (s, "") -> Ok s + | Ok _ -> Error "Unexpected character after closing double-quote in header" + | Error _ as err -> err + +let parse_git_header_rename ~from_ ~to_ s = + let rec loop ~s ~len i = + if i < (len : int) then + match String.unsafe_get s i with | ' ' | '\t' -> - let a = parse (Lib.String.slice ~stop:i s) in - let b = parse (Lib.String.slice ~start:(i + 1) s) in + let a = parse_git_filename (Lib.String.slice ~stop:i s) in + let b = parse_git_filename (Lib.String.slice ~start:(i + 1) s) in begin match a, b with - | Ok (Some ("a", a)), Ok (Some ("b", b)) - when a = (b : string) -> - Some a - | _, _ -> loop s len (i + 1) + | Ok a, Ok b + when Lib.String.is_suffix ~suffix:from_ a && + Lib.String.is_suffix ~suffix:to_ b + -> Some (a, b) + | Ok _, Ok _ | Error _, _ | _, Error _ + -> loop ~s ~len (i + 1) end - | _ -> loop s len (i + 1) + | _ -> loop ~s ~len (i + 1) else None in - loop s (String.length s) 0 + loop ~s ~len:(String.length s) 0 + +let parse_git_header_same s = + let rec loop ~best ~s ~len i = + if i < (len : int) then + match String.unsafe_get s i with + | ' ' | '\t' -> + let a = parse_git_filename (Lib.String.slice ~stop:i s) in + let b = parse_git_filename (Lib.String.slice ~start:(i + 1) s) in + begin match a, b with + | Ok a, Ok b -> + begin match best, Lib.String.count_common_suffix a b with + | None, best -> loop ~best:(Some (best, a, b)) ~s ~len (i + 1) + | Some (prev_best, _, _), best when best > (prev_best : int) -> + loop ~best:(Some (best, a, b)) ~s ~len (i + 1) + | Some _ as best, _ -> loop ~best ~s ~len (i + 1) + end + | Error _, _ | _, Error _ -> loop ~best ~s ~len (i + 1) + end + | _ -> loop ~best ~s ~len (i + 1) + else + match best with + | None -> None + | Some (_best, a, b) -> Some (a, b) + in + loop ~best:None ~s ~len:(String.length s) 0 diff --git a/src/fname.mli b/src/fname.mli index 7640264..8285a45 100644 --- a/src/fname.mli +++ b/src/fname.mli @@ -4,4 +4,15 @@ val parse : string -> (string option, string) result Returns [Error msg] in case of error. *) -val parse_git_header : string -> string option +(** {1 Git header parsers} *) + +val parse_git_header_rename : + from_:string -> to_:string -> string -> (string * string) option +(** [parse_git_header_rename ~from_ ~to_ str] will parse [str] by trying to + match [from_] and [to_] on the left side and right side of the space split + respectively. Returns [None] if nothing was able to be found. *) + +val parse_git_header_same : string -> (string * string) option +(** [parse_git_header_same str] will parse [str] by trying to get the largest + equal suffix for both filenames in the git header. Returns [None] if + nothing was able to be found. *) diff --git a/src/lib.ml b/src/lib.ml index a45eb44..95b496f 100644 --- a/src/lib.ml +++ b/src/lib.ml @@ -38,6 +38,18 @@ module String = struct in let len = stop - start in String.sub str start len + + let count_common_suffix x y = + let rec loop ~x ~y acc ix iy = + if ix >= 0 && iy >= 0 && + String.unsafe_get x ix = (String.unsafe_get y iy : char) then + loop ~x ~y (acc + 1) (ix - 1) (iy - 1) + else + acc + in + let len_x = String.length x in + let len_y = String.length y in + loop ~x ~y 0 (len_x - 1) (len_y - 1) end module List = struct diff --git a/src/lib.mli b/src/lib.mli index 9a4fa72..fe6d438 100644 --- a/src/lib.mli +++ b/src/lib.mli @@ -4,6 +4,7 @@ module String : sig val cut : char -> string -> (string * string) option val cuts : char -> string -> string list val slice : ?start:int -> ?stop:int -> string -> string + val count_common_suffix : string -> string -> int end module List : sig diff --git a/src/patch.ml b/src/patch.ml index c724ecc..d6066fa 100644 --- a/src/patch.ml +++ b/src/patch.ml @@ -202,18 +202,36 @@ let rec to_hunks (mine_no_nl, their_no_nl, acc) = function | None, mine_no_nl, their_no_nl, rest -> List.rev acc, mine_no_nl, their_no_nl, rest | Some hunk, mine_no_nl, their_no_nl, rest -> to_hunks (mine_no_nl, their_no_nl, hunk :: acc) rest +type git_ext = + | Rename_only of string * string + | Delete_only + | Create_only + type operation = | Edit of string * string | Delete of string | Create of string - | Rename_only of string * string + | Git_ext of (string * string * git_ext) + +let git_ext_eq a b = match a, b with + | Delete_only, Delete_only + | Create_only, Create_only + -> true + | Rename_only (a, b), Rename_only (a', b') + -> String.equal a a' && String.equal b b' + | Rename_only _, _ | Delete_only, _ | Create_only, _ + -> false let operation_eq a b = match a, b with | Delete a, Delete b - | Create a, Create b -> String.equal a b - | Edit (a, a'), Edit (b, b') - | Rename_only (a, a'), Rename_only (b, b') -> String.equal a b && String.equal a' b' - | Delete _, _ | Create _, _ | Edit _, _ | Rename_only _, _ -> false + | Create a, Create b + -> String.equal a b + | Edit (a, b), Edit (a', b') + -> String.equal a a' && String.equal b b' + | Git_ext (a, b, ext1), Git_ext (a', b', ext2) + -> String.equal a a' && String.equal b b' && git_ext_eq ext1 ext2 + | Edit _, _ | Delete _, _ | Create _, _ | Git_ext _, _ + -> false let no_file = "/dev/null" @@ -257,8 +275,7 @@ let pp_filename ppf fn = else Format.pp_print_text ppf fn -let pp_operation ppf op = - match op with +let pp_operation ppf = function | Edit (old_name, new_name) -> Format.fprintf ppf "--- %a\n" pp_filename old_name ; Format.fprintf ppf "+++ %a\n" pp_filename new_name @@ -268,10 +285,16 @@ let pp_operation ppf op = | Create name -> Format.fprintf ppf "--- %a\n" pp_filename no_file ; Format.fprintf ppf "+++ %a\n" pp_filename name - | Rename_only (old_name, new_name) -> - Format.fprintf ppf "diff --git %a %a\n" pp_filename old_name pp_filename new_name; - Format.fprintf ppf "rename from %a\n" pp_filename old_name; - Format.fprintf ppf "rename to %a\n" pp_filename new_name + | Git_ext (a, b, ext) -> + Format.fprintf ppf "diff --git %a %a\n" pp_filename a pp_filename b; + match ext with + | Rename_only (from_, to_) -> + Format.fprintf ppf "rename from %a\n" pp_filename from_; + Format.fprintf ppf "rename to %a\n" pp_filename to_ + | Delete_only -> + Format.pp_print_string ppf "deleted file mode 100644\n"; + | Create_only -> + Format.pp_print_string ppf "new file mode 100644\n"; type t = { operation : operation ; @@ -283,7 +306,12 @@ type t = { let pp ppf {operation; hunks; mine_no_nl; their_no_nl} = pp_operation ppf operation; let rec aux = function - | [] -> () + | [] -> + begin match operation with + | Edit _ | Delete _ | Create _ -> + assert false + | Git_ext _ -> () (* already delt with in pp_operation *) + end | [x] -> pp_hunk ~mine_no_nl ~their_no_nl ppf x | x::xs -> pp_hunk ~mine_no_nl:false ~their_no_nl:false ppf x; @@ -327,7 +355,7 @@ let operation_of_strings ~p mine their = let parse_one ~p data = let open (struct - type mode = Git of string option + type mode = Git of string end) in let is_git = function | Some (Git _) -> true @@ -335,32 +363,75 @@ let parse_one ~p data = in (* first locate --- and +++ lines *) let rec find_start ~mode ~git_action = function - | [] -> git_action, [] + | [] -> + begin match git_action with + | Some git_action -> Some (Git_ext git_action), [] + | None -> None, [] + end | x::xs when Lib.String.is_prefix ~prefix:"diff --git " x -> - let git_filename = Fname.parse_git_header (Lib.String.slice ~start:11 x) in begin match mode, git_action with - | (None | Some (Git _)), None -> find_start ~mode:(Some (Git git_filename)) ~git_action:None xs + | (None | Some (Git _)), None -> find_start ~mode:(Some (Git x)) ~git_action:None xs | None, Some _ -> assert false (* impossible state *) - | Some (Git _), Some git_action -> (Some git_action, x :: xs) + | Some (Git _), Some git_action -> (Some (Git_ext git_action), x :: xs) end | x::y::xs when is_git mode && Lib.String.is_prefix ~prefix:"rename from " x && Lib.String.is_prefix ~prefix:"rename to " y -> - let git_action = Some (Rename_only (Lib.String.slice ~start:12 x, Lib.String.slice ~start:10 y)) in - find_start ~mode ~git_action xs + let git_action = match mode with + | None -> assert false + | Some (Git git_filenames) -> + let from_ = Lib.String.slice ~start:12 x in + let to_ = Lib.String.slice ~start:10 y in + let git_filenames = Lib.String.slice ~start:11 git_filenames in + match Fname.parse_git_header_rename ~from_ ~to_ git_filenames with + | None -> git_action + | Some (a, b) -> + let a = strip_prefix ~p a in + let b = strip_prefix ~p b in + Some (a, b, Rename_only (from_, to_)) + in + find_start ~mode ~git_action xs | x::xs when is_git mode && Lib.String.is_prefix ~prefix:"deleted file mode " x -> let git_action = match mode with - | Some (Git (Some git_filename)) -> Some (Delete git_filename) - | Some (Git None) -> git_action | None -> assert false + | Some (Git git_filenames) -> + let git_filenames = Lib.String.slice ~start:11 git_filenames in + match Fname.parse_git_header_same git_filenames with + | None -> git_action + | Some (a, b) -> + let a = strip_prefix ~p a in + let b = strip_prefix ~p b in + Some (a, b, Delete_only) + in + find_start ~mode ~git_action xs + | x::xs when is_git mode && Lib.String.is_prefix ~prefix:"new file mode " x -> + let git_action = match mode with + | None -> assert false + | Some (Git git_filenames) -> + let git_filenames = Lib.String.slice ~start:11 git_filenames in + match Fname.parse_git_header_same git_filenames with + | None -> git_action + | Some (a, b) -> + let a = strip_prefix ~p a in + let b = strip_prefix ~p b in + Some (a, b, Create_only) in find_start ~mode ~git_action xs | x::y::xs when Lib.String.is_prefix ~prefix:"--- " x && Lib.String.is_prefix ~prefix:"+++ " y -> - Some (operation_of_strings ~p x y), xs + begin match git_action, operation_of_strings ~p x y with + | None, op -> Some op, xs + | Some (f, _, Delete_only), (Delete f' as op) + | Some (_, f, Create_only), (Create f' as op) + when String.equal f f' -> Some op, xs + | Some (a, b, Rename_only (_, _)), (Edit (a', b') as op) + when String.equal a a' && String.equal b b' -> Some op, xs + | Some (_, _, (Rename_only _ | Delete_only | Create_only) as git_op), _ + -> Some (Git_ext git_op), x :: y :: xs + end | x::y::_xs when Lib.String.is_prefix ~prefix:"*** " x && Lib.String.is_prefix ~prefix:"--- " y -> failwith "Context diffs are not supported" | _::xs -> find_start ~mode ~git_action xs in match find_start ~mode:None ~git_action:None data with - | Some (Rename_only _ as operation), rest -> + | Some (Git_ext _ as operation), rest -> let hunks = [] and mine_no_nl = false and their_no_nl = false in Some ({ operation ; hunks ; mine_no_nl ; their_no_nl }, rest) | Some operation, rest -> @@ -383,7 +454,14 @@ let parse ~p data = let patch ~cleanly filedata diff = match diff.operation with - | Rename_only _ -> filedata + | Git_ext (_, _, ext) -> + if diff.hunks <> [] then + assert false; + begin match ext with + | Rename_only _ -> filedata + | Delete_only -> None + | Create_only -> Some "" + end | Delete _ -> None | Create _ -> begin match diff.hunks with @@ -461,9 +539,15 @@ let diff_op operation a b = ~their_start:(if b = "" then 0 else 1) ~their_len:0 ~their:[] (to_lines a) (to_lines b) -let diff operation a b = match a, b with +let diff a b = match a, b with | None, None -> invalid_arg "no input given" - | None, Some b -> diff_op operation "" b - | Some a, None -> diff_op operation a "" - | Some a, Some b when String.equal a b -> None (* NOTE: Optimization *) - | Some a, Some b -> diff_op operation a b + | None, Some (filename_b, "") -> + Some { operation = Git_ext (filename_b, filename_b, Create_only); + hunks = []; mine_no_nl = true; their_no_nl = true; } + | Some (filename_a, ""), None -> + Some { operation = Git_ext (filename_a, filename_a, Delete_only); + hunks = []; mine_no_nl = true; their_no_nl = true; } + | None, Some (filename_b, b) -> diff_op (Create filename_b) "" b + | Some (filename_a, a), None -> diff_op (Delete filename_a) a "" + | Some (_, a), Some (_, b) when String.equal a b -> None (* NOTE: Optimization *) + | Some (filename_a, a), Some (filename_b, b) -> diff_op (Edit (filename_a, filename_b)) a b diff --git a/src/patch.mli b/src/patch.mli index 50f3031..b04200f 100644 --- a/src/patch.mli +++ b/src/patch.mli @@ -22,14 +22,34 @@ val pp_hunk : mine_no_nl:bool -> their_no_nl:bool -> Format.formatter -> hunk -> (** [pp_hunk ppf hunk] pretty-prints the [hunk] on [ppf], the printing is in the same format as [diff] does. *) +type git_ext = + | Rename_only of string * string + | Delete_only + | Create_only + type operation = | Edit of string * string | Delete of string | Create of string - | Rename_only of string * string - (** The operation of a diff: in-place [Edit], [Delete], [Create], [Rename_only]. + | Git_ext of (string * string * git_ext) + (** The operation of a diff: in-place [Edit], [Delete], [Create]. + And its git-extensions: [Rename_only], [Delete_only], [Create_only]. The parameters to the variants are filenames. - NOTE: in a typical git diff file, [Rename_only] does not have any prefix. *) + + Note that [Edit] also renames the given file under certain conditions + and the file to use is driven by this POSIX rule: + https://pubs.opengroup.org/onlinepubs/9799919799/utilities/patch.html#tag_20_92_13_02 + + Note also that the two filenames in [Git_ext] represent what would be + in [git --diff ] with their respective prefixes + removed if parsed with [parse ~p:1] or above. + + Warning: The two parameters of [Rename_only] represent the values of the + [rename from ] and [rename to ] following the + specs of the git extensions. Following the behaviour of GNU Patch which + ignores these two lines, it is recommended to get the filenames from + [Git_ext] instead of from [Rename_only], which are used only for + pretty-printing. *) val pp_operation : Format.formatter -> operation -> unit (** [pp_operation ppf op] pretty-prints the operation [op] on [ppf]. *) @@ -65,8 +85,8 @@ val patch : cleanly:bool -> string option -> t -> string option (** [patch file_contents diff] applies [diff] on [file_contents], resulting in the new file contents (or None if deleted). *) -val diff : operation -> string option -> string option -> t option -(** [diff operation content_a content_b] creates a diff between +val diff : (string * string) option -> (string * string) option -> t option +(** [diff (filename_a, content_a) (filename_b, content_b)] creates a diff between [content_a] and [content_b]. Returns [None] if no changes could be detected. - @raise Invalid_argument if both [content_a] and [content_b] are [None]. *) + @raise Invalid_argument if both arguments are [None]. *) diff --git a/test/data/create-empty-only.diff b/test/data/create-empty-only.diff new file mode 100644 index 0000000..2598c63 --- /dev/null +++ b/test/data/create-empty-only.diff @@ -0,0 +1,3 @@ +diff --git b/create-empty-only b/create-empty-only +new file mode 100644 +index 0000000..e69de29 diff --git a/test/data/create-empty-only.new b/test/data/create-empty-only.new new file mode 100644 index 0000000..e69de29 diff --git a/test/data/create-newline-only.diff b/test/data/create-newline-only.diff new file mode 100644 index 0000000..91e62ba --- /dev/null +++ b/test/data/create-newline-only.diff @@ -0,0 +1,5 @@ +diff -ruN a/create-newline-only b/create-newline-only +--- a/create-newline-only 1970-01-01 01:00:00.000000000 +0100 ++++ b/create-newline-only 2025-04-30 19:12:35.180584541 +0100 +@@ -0,0 +1 @@ ++ diff --git a/test/data/create-newline-only.new b/test/data/create-newline-only.new new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/test/data/create-newline-only.new @@ -0,0 +1 @@ + diff --git a/test/data/delete-empty-only.diff b/test/data/delete-empty-only.diff new file mode 100644 index 0000000..ff9bbbd --- /dev/null +++ b/test/data/delete-empty-only.diff @@ -0,0 +1,3 @@ +diff --git b/delete-empty-only b/delete-empty-only +deleted file mode 100644 +index e69de29..0000000 diff --git a/test/data/delete-empty-only.old b/test/data/delete-empty-only.old new file mode 100644 index 0000000..e69de29 diff --git a/test/data/delete-newline-only.diff b/test/data/delete-newline-only.diff new file mode 100644 index 0000000..ffa609a --- /dev/null +++ b/test/data/delete-newline-only.diff @@ -0,0 +1,5 @@ +diff -ruN b/delete-newline-only a/delete-newline-only +--- b/delete-newline-only 2025-04-30 19:12:35.180584541 +0100 ++++ a/delete-newline-only 1970-01-01 01:00:00.000000000 +0100 +@@ -1 +0,0 @@ +- diff --git a/test/data/delete-newline-only.old b/test/data/delete-newline-only.old new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/test/data/delete-newline-only.old @@ -0,0 +1 @@ + diff --git a/test/data/external b/test/data/external index 391cc9a..27ee0cb 160000 --- a/test/data/external +++ b/test/data/external @@ -1 +1 @@ -Subproject commit 391cc9aa1c58e0547b80901f46826c6ce3181753 +Subproject commit 27ee0cb7548e650585fbe1518128155131590854 diff --git a/test/opam-repository/test.sh b/test/opam-repository/test.sh index 4ce3082..5df28c5 100755 --- a/test/opam-repository/test.sh +++ b/test/opam-repository/test.sh @@ -19,11 +19,18 @@ read -r NEW_OPAM echo echo '## Getting the list of packages with patches' echo +rm -f pkgs-with-patches rm -rf ./opam-repository -git clone -b 2025-01-before-archiving-phase1 --depth=1 https://github.com/ocaml/opam-repository.git +git clone --depth=1 https://github.com/ocaml/opam-repository.git pushd ./opam-repository/packages > /dev/null -grep -l '^patches:' */*/opam | cut -d/ -f2 > ../../pkgs-with-patches +grep -l '^patches:' */*/opam | cut -d/ -f2 >> ../../pkgs-with-patches +popd > /dev/null + +rm -rf ./opam-repository-archive +git clone --depth=1 https://github.com/ocaml/opam-repository-archive.git +pushd ./opam-repository-archive/packages > /dev/null +grep -l '^patches:' */*/opam | cut -d/ -f2 >> ../../pkgs-with-patches popd > /dev/null NB_OF_PKGS=$(cat ./pkgs-with-patches | wc -l) @@ -44,6 +51,7 @@ rm -rf ./tmp ./old mkdir ./tmp pushd ./tmp > /dev/null "${OLD_OPAM}" init --bare --no-setup --no-opamrc ../opam-repository +"${OLD_OPAM}" repository add --set-default archive ../opam-repository-archive cat ../pkgs-with-patches | time -p xargs -n1 "${OLD_OPAM}" source > ../old.log 2>&1 || true rm_faulty_pkg_artefacts rm -rf "${OPAMROOT}" @@ -58,6 +66,7 @@ rm -rf ./tmp ./new mkdir ./tmp pushd ./tmp > /dev/null "${NEW_OPAM}" init --bare --no-setup --no-opamrc ../opam-repository +"${NEW_OPAM}" repository add --set-default archive ../opam-repository-archive cat ../pkgs-with-patches | time -p xargs -n1 "${NEW_OPAM}" source > ../new.log 2>&1 || true rm_faulty_pkg_artefacts rm -rf "${OPAMROOT}" diff --git a/test/test.ml b/test/test.ml index 4d1a209..ccb0e0a 100644 --- a/test/test.ml +++ b/test/test.ml @@ -434,7 +434,8 @@ let parse_real_diff_headers = [ "first", Patch.Edit ("first.old", "first.new") ; "create1", Patch.Create "a/create1" ; "git1", Patch.Create "b/git1.new" ; - "git2", Patch.Rename_only ("git2.old", "git2.new") ; + "git2", Patch.Git_ext ("a/git2.old", "b/git2.new", + Patch.Rename_only ("git2.old", "git2.new")) ; "git3", Patch.Edit ("a/git3.old", "b/git3.new") ; "git4", Patch.Delete "a/git4.old" ] @@ -486,7 +487,7 @@ ccc ddd eee|}^(if their_no_nl then "" else "\n") in - let diff = Patch.diff (Create "b") a (Some b) in + let diff = Patch.diff a (Some ("b", b)) in let hunk = { Patch.operation = Create "b"; hunks = [ { mine_start = 0; mine_len = 0; mine = []; @@ -509,7 +510,7 @@ ddd eee|}^(if mine_no_nl then "" else "\n") and b = None in - let diff = Patch.diff (Delete "a") (Some a) b in + let diff = Patch.diff (Some ("a", a)) b in let hunk = { Patch.operation = Delete "a"; hunks = [ { mine_start = 1; mine_len = 5; mine = ["aaa"; "bbb"; "ccc"; "ddd"; "eee"]; @@ -526,7 +527,7 @@ let diff_tests_their_unavailable_none_no_nl, diff_tests_hunk_their_unavailable_n let diff_tests_empty_gen ~mine_no_nl ~their_no_nl = let a = if mine_no_nl then "" else "\n" and b = if their_no_nl then "" else "\n" in - let diff = Patch.diff (Edit ("a", "b")) (Some a) (Some b) in + let diff = Patch.diff (Some ("a", a)) (Some ("b", b)) in let hunk = if (mine_no_nl && their_no_nl) || (not mine_no_nl && not their_no_nl) then None @@ -563,7 +564,7 @@ ccc ddd eee|}^(if their_no_nl then "" else "\n") in - let diff = Patch.diff (Edit ("a", "b")) (Some a) (Some b) in + let diff = Patch.diff (Some ("a", a)) (Some ("b", b)) in let hunk = if (mine_no_nl && their_no_nl) || (not mine_no_nl && not their_no_nl) then None @@ -598,7 +599,7 @@ test1 test2 eee|}^(if their_no_nl then "" else "\n") in - let diff = Patch.diff (Edit ("a", "b")) (Some a) (Some b) in + let diff = Patch.diff (Some ("a", a)) (Some ("b", b)) in let hunk = { Patch.operation = Edit ("a", "b"); hunks = [ { mine_start = 3; mine_len = 3; mine = ["ccc"; "ddd"; "eee"]; @@ -629,7 +630,7 @@ bbb test1 eee|}^(if their_no_nl then "" else "\n") in - let diff = Patch.diff (Edit ("a", "b")) (Some a) (Some b) in + let diff = Patch.diff (Some ("a", a)) (Some ("b", b)) in let hunk = { Patch.operation = Edit ("a", "b"); hunks = [ { mine_start = 3; mine_len = 3; mine = ["ccc"; "ddd"; "eee"]; @@ -661,7 +662,7 @@ ccc ddd eee|}^(if their_no_nl then "" else "\n") in - let diff = Patch.diff (Edit ("a", "b")) (Some a) (Some b) in + let diff = Patch.diff (Some ("a", a)) (Some ("b", b)) in let hunk = { Patch.operation = Edit ("a", "b"); hunks = [ { mine_start = 1; mine_len = 5; mine = ["aaa"; "bbb"; "ccc"; "ddd"; "eee"]; @@ -692,7 +693,7 @@ ccc ddd eee|}^(if their_no_nl then "" else "\n") in - let diff = Patch.diff (Edit ("a", "b")) (Some a) (Some b) in + let diff = Patch.diff (Some ("a", a)) (Some ("b", b)) in let hunk = { Patch.operation = Edit ("a", "b"); hunks = [ { mine_start = 1; mine_len = 5; mine = ["aaa"; "bbb"; "ccc"; "ddd"; "eee"]; @@ -724,7 +725,7 @@ ccc ddd test1|}^(if their_no_nl then "" else "\n") in - let diff = Patch.diff (Edit ("a", "b")) (Some a) (Some b) in + let diff = Patch.diff (Some ("a", a)) (Some ("b", b)) in let hunk = { Patch.operation = Edit ("a", "b"); hunks = [ { mine_start = 5; mine_len = 1; mine = ["eee"]; @@ -755,7 +756,7 @@ bbb ccc test1|}^(if their_no_nl then "" else "\n") in - let diff = Patch.diff (Edit ("a", "b")) (Some a) (Some b) in + let diff = Patch.diff (Some ("a", a)) (Some ("b", b)) in let hunk = { Patch.operation = Edit ("a", "b"); hunks = [ { mine_start = 4; mine_len = 2; mine = ["ddd"; "eee"]; @@ -833,8 +834,8 @@ let unified_diff_spaces = let git_diff_spaces = {|\ diff --git a/foo bar b/foo bar index ef00db3..88adca3 100644 ---- a/foo bar -+++ b/foo bar +--- a/foo bar|}^"\t"^{| ++++ b/foo bar|}^"\t"^{| @@ -1 +1 @@ -This is wrong. +This is right. @@ -1072,10 +1073,20 @@ let print_big () = Alcotest.(check string) __LOC__ expected actual | None, _ | _, None -> Alcotest.skip () else Alcotest.skip () +let parse_own () = + if support_string_length_above_20MB then + match Lazy.force expected with + | Some expected -> + let patch = Patch.parse ~p:0 expected in + let actual = Format.asprintf "%a" Patch.pp_list patch in + Alcotest.(check string) __LOC__ expected actual + | None -> Alcotest.skip () + else Alcotest.skip () let big_diff = [ "parse", `Quick, parse_big; "print", `Quick, print_big; + "parse own", `Quick, parse_own; ] let tests = [