Skip to content

Commit 57366b4

Browse files
kit-ty-katehannesm
andcommitted
Add a ~p parameter to Patch.parse mimicking the behaviour of patch -p<num>
Co-authored-by: Hannes Mehnert <[email protected]>
1 parent 81d31bd commit 57366b4

File tree

5 files changed

+117
-89
lines changed

5 files changed

+117
-89
lines changed

src/patch.ml

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -161,33 +161,18 @@ let operation_eq a b = match a, b with
161161

162162
let no_file = "/dev/null"
163163

164-
let pp_operation ~git ppf op =
165-
let real_name direction name =
166-
if git then name else
167-
match direction with `Mine -> "a/" ^ name | `Theirs -> "b/" ^ name
168-
in
169-
let hdr mine their =
170-
(* even if create/delete, /dev/null is not used in this header *)
171-
(* according to git documentation *)
172-
if git then
173-
Format.fprintf ppf "diff --git %s %s\n"
174-
(real_name `Mine mine) (real_name `Theirs their)
175-
in
164+
let pp_operation ppf op =
176165
match op with
177166
| Edit (old_name, new_name) ->
178-
hdr old_name new_name ;
179-
Format.fprintf ppf "--- %s\n" (real_name `Mine old_name) ;
180-
Format.fprintf ppf "+++ %s\n" (real_name `Theirs new_name)
167+
Format.fprintf ppf "--- %s\n" old_name ;
168+
Format.fprintf ppf "+++ %s\n" new_name
181169
| Delete name ->
182-
hdr name name ;
183-
Format.fprintf ppf "--- %s\n" (real_name `Mine name) ;
170+
Format.fprintf ppf "--- %s\n" name ;
184171
Format.fprintf ppf "+++ %s\n" no_file
185172
| Create name ->
186-
hdr name name ;
187173
Format.fprintf ppf "--- %s\n" no_file ;
188-
Format.fprintf ppf "+++ %s\n" (real_name `Theirs name)
174+
Format.fprintf ppf "+++ %s\n" name
189175
| Rename_only (old_name, new_name) ->
190-
hdr old_name new_name ;
191176
Format.fprintf ppf "rename from %s\n" old_name;
192177
Format.fprintf ppf "rename to %s\n" new_name
193178

@@ -198,8 +183,8 @@ type t = {
198183
their_no_nl : bool ;
199184
}
200185

201-
let pp ~git ppf {operation; hunks; mine_no_nl; their_no_nl} =
202-
pp_operation ~git ppf operation;
186+
let pp ppf {operation; hunks; mine_no_nl; their_no_nl} =
187+
pp_operation ppf operation;
203188
let rec aux = function
204189
| [] -> ()
205190
| [x] -> pp_hunk ~mine_no_nl ~their_no_nl ppf x
@@ -209,48 +194,43 @@ let pp ~git ppf {operation; hunks; mine_no_nl; their_no_nl} =
209194
in
210195
aux hunks
211196

212-
let pp_list ~git ppf diffs =
213-
List.iter (Format.fprintf ppf "%a" (pp ~git)) diffs
197+
let pp_list ppf diffs =
198+
List.iter (Format.fprintf ppf "%a" pp) diffs
214199

215-
(* TODO: remove this and let users decide the prefix level they want *)
216-
let process_git_prefix ~git ~prefix s =
217-
if git && String.is_prefix ~prefix s then
218-
String.slice ~start:(String.length prefix) s
200+
let strip_prefix ~p filename =
201+
if p = 0 then
202+
filename
219203
else
220-
s
204+
match String.cuts '/' filename with
205+
| [] -> assert false
206+
| x::xs ->
207+
(* Per GNU patch's spec: A sequence of one or more adjacent slashes is counted as a single slash. *)
208+
let filename = x :: List.filter (function "" -> false | _ -> true) xs in
209+
String.concat "/" (drop filename p)
221210

222-
let operation_of_strings git mine their =
211+
let operation_of_strings ~p mine their =
223212
let mine_fn = String.slice ~start:4 mine
224213
and their_fn = String.slice ~start:4 their in
225214
match Fname.parse mine_fn, Fname.parse their_fn with
226-
| Ok None, Ok (Some b) ->
227-
let b = process_git_prefix ~git ~prefix:"b/" b in
228-
Create b
229-
| Ok (Some a), Ok None ->
230-
let a = process_git_prefix ~git ~prefix:"a/" a in
231-
Delete a
232-
| Ok (Some a), Ok (Some b) ->
233-
let a = process_git_prefix ~git ~prefix:"a/" a in
234-
let b = process_git_prefix ~git ~prefix:"b/" b in
235-
Edit (a, b)
215+
| Ok None, Ok (Some b) -> Create (strip_prefix ~p b)
216+
| Ok (Some a), Ok None -> Delete (strip_prefix ~p a)
217+
| Ok (Some a), Ok (Some b) -> Edit (strip_prefix ~p a, strip_prefix ~p b)
236218
| Ok None, Ok None -> assert false (* ??!?? *)
237219
| Error msg, _ -> raise (Parse_error {msg; lines = [mine]})
238220
| _, Error msg -> raise (Parse_error {msg; lines = [their]})
239221

240-
let parse_one data =
222+
let parse_one ~p data =
241223
(* first locate --- and +++ lines *)
242-
let rec find_start git ?hdr = function
224+
let rec find_start ?hdr = function
243225
| [] -> hdr, []
244-
| x::xs when String.is_prefix ~prefix:"diff --git " x ->
245-
begin match hdr with None -> find_start true xs | Some _ -> hdr, x::xs end
246226
| x::y::xs when String.is_prefix ~prefix:"rename from " x && String.is_prefix ~prefix:"rename to " y ->
247227
let hdr = Rename_only (String.slice ~start:12 x, String.slice ~start:10 y) in
248-
find_start git ~hdr xs
228+
find_start ~hdr xs
249229
| x::y::xs when String.is_prefix ~prefix:"--- " x ->
250-
Some (operation_of_strings git x y), xs
251-
| _::xs -> find_start git ?hdr xs
230+
Some (operation_of_strings ~p x y), xs
231+
| _::xs -> find_start ?hdr xs
252232
in
253-
match find_start false data with
233+
match find_start data with
254234
| Some (Rename_only _ as operation), rest ->
255235
let hunks = [] and mine_no_nl = false and their_no_nl = false in
256236
Some ({ operation ; hunks ; mine_no_nl ; their_no_nl }, rest)
@@ -262,15 +242,15 @@ let parse_one data =
262242

263243
let to_lines = String.cuts '\n'
264244

265-
let parse data =
245+
let parse ~p data =
266246
let lines = to_lines data in
267-
let rec doit acc = function
247+
let rec doit ~p acc = function
268248
| [] -> List.rev acc
269-
| xs -> match parse_one xs with
249+
| xs -> match parse_one ~p xs with
270250
| None -> List.rev acc
271-
| Some (diff, rest) -> doit (diff :: acc) rest
251+
| Some (diff, rest) -> doit ~p (diff :: acc) rest
272252
in
273-
doit [] lines
253+
doit ~p [] lines
274254

275255
let patch filedata diff =
276256
match diff.operation with

src/patch.mli

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,11 @@ type operation =
2828
| Create of string
2929
| Rename_only of string * string
3030
(** The operation of a diff: in-place [Edit], [Delete], [Create], [Rename_only].
31-
The parameters to the variants are filenames. *)
31+
The parameters to the variants are filenames.
32+
NOTE: in a typical git diff file, [Rename_only] does not have any prefix. *)
3233

33-
val pp_operation : git:bool -> Format.formatter -> operation -> unit
34-
(** [pp_operation ~git ppf op] pretty-prints the operation [op] on [ppf], If
35-
[git] is true, the [git diff] style will be output (a
36-
"diff --git oldfilename newfilename" line, etc). *)
34+
val pp_operation : Format.formatter -> operation -> unit
35+
(** [pp_operation ppf op] pretty-prints the operation [op] on [ppf]. *)
3736

3837
val operation_eq : operation -> operation -> bool
3938
(** [operation_eq a b] is true if [a] and [b] are equal. *)
@@ -47,16 +46,18 @@ type t = {
4746
(** The type of a diff: an operation, a list of hunks, and information whether
4847
a trailing newline exists on the left and right. *)
4948

50-
val pp : git:bool -> Format.formatter -> t -> unit
51-
(** [pp ~git ppf t] pretty-prints [t] on [ppf]. If [git] is true, "git diff"
52-
style will be printed. *)
49+
val pp : Format.formatter -> t -> unit
50+
(** [pp ppf t] pretty-prints [t] on [ppf]. *)
5351

54-
val pp_list : git:bool -> Format.formatter -> t list -> unit
55-
(** [pp ~git ppf diffs] pretty-prints [diffs] on [ppf]. If [git] is true,
56-
"git diff" style will be printed. *)
52+
val pp_list : Format.formatter -> t list -> unit
53+
(** [pp ppf diffs] pretty-prints [diffs] on [ppf]. *)
5754

58-
val parse : string -> t list
59-
(** [parse data] decodes [data] as a list of diffs.
55+
val parse : p:int -> string -> t list
56+
(** [parse ~p data] decodes [data] as a list of diffs.
57+
58+
@param p denotes the expected prefix level of the filenames.
59+
For more information, see the option [-p] in your POSIX-complient
60+
patch.
6061
6162
@raise Parse_error if a filename was unable to be parsed *)
6263

src/patch_command.ml

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66

77
let usage =
88
"Simplified patch utility for single-file patches;\n
9-
./patch.exe <input-file> <unififed-diff-file> -o <output-file>"
9+
./patch.exe -p<num> <input-file> <unified-diff-file> -o <output-file>"
1010

1111
let exit_command_line_error = 1
1212
let exit_open_error = 2
1313
let exit_several_chunks = 3
1414
let exit_patch_failure = 4
1515

16-
let run ~input ~diff =
17-
match Patch.parse diff with
16+
let run ~p ~input ~diff =
17+
match Patch.parse ~p diff with
1818
| [] -> input
1919
| _::_::_ ->
2020
prerr_endline "Error: The diff contains several chunks,\n\
@@ -48,12 +48,17 @@ let () =
4848
prerr_endline usage;
4949
exit 0;
5050
end;
51-
let input_path, diff_path, output_path = try
52-
let input_path = Sys.argv.(1) in
53-
let diff_path = Sys.argv.(2) in
54-
let dash_o = Sys.argv.(3) in
55-
let output_path = Sys.argv.(4) in
51+
let p, input_path, diff_path, output_path = try
52+
let p =
53+
let arg = Sys.argv.(1) in
54+
String.sub arg 2 (String.length arg - 2) |> int_of_string
55+
in
56+
let input_path = Sys.argv.(2) in
57+
let diff_path = Sys.argv.(3) in
58+
let dash_o = Sys.argv.(4) in
59+
let output_path = Sys.argv.(5) in
5660
if dash_o <> "-o" then raise Exit;
61+
p,
5762
input_path,
5863
diff_path,
5964
output_path
@@ -84,5 +89,5 @@ let () =
8489
in
8590
let input_data = get_data input_path in
8691
let diff_data = get_data diff_path in
87-
let output_data = run ~input:input_data ~diff:diff_data in
92+
let output_data = run ~p ~input:input_data ~diff:diff_data in
8893
write_data output_path ~data:output_data

test/crowbar_test.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ let get_diffs (file1 : file) (file2 : file) : file =
136136

137137
let check_Patch file1 file2 =
138138
let text_diff = string_of_file (get_diffs file1 file2) in
139-
match Patch.parse text_diff with
139+
match Patch.parse ~p:0 text_diff with
140140
| [] -> Crowbar.check_eq (string_of_file file1) (string_of_file file2)
141141
| _::_::_ -> Crowbar.fail "not a single diff!"
142142
| [diff] ->

test/test.ml

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ let patch_eq a b =
1616
List.length a.hunks = List.length b.hunks &&
1717
List.for_all (fun h -> List.exists (fun h' -> hunk_eq h h') b.hunks) a.hunks
1818

19-
let test_t = Alcotest.testable (Patch.pp ~git:false) patch_eq
19+
let test_t = Alcotest.testable Patch.pp patch_eq
2020

2121
let basic_files = [
2222
Some "foo\n" ;
@@ -295,7 +295,7 @@ foo
295295
]
296296

297297
let basic_parse diff exp () =
298-
let diffs = Patch.parse diff in
298+
let diffs = Patch.parse ~p:0 diff in
299299
Alcotest.(check (list test_t) __LOC__ exp diffs)
300300

301301
let parse_diffs =
@@ -304,7 +304,7 @@ let parse_diffs =
304304
(List.combine basic_diffs basic_hunks)
305305

306306
let basic_apply file diff exp () =
307-
match Patch.parse diff with
307+
match Patch.parse ~p:0 diff with
308308
| [ diff ] ->
309309
let res = Patch.patch file diff in
310310
Alcotest.(check (option string) __LOC__ exp res)
@@ -367,7 +367,7 @@ let multi_files = [ Some "bar" ; Some "baz" ; None ; Some "foobarbaz" ]
367367
let multi_exp = [ Some "foobar" ; None ; Some "baz" ; Some "foobar" ]
368368

369369
let multi_apply () =
370-
let diffs = Patch.parse multi_diff in
370+
let diffs = Patch.parse ~p:0 multi_diff in
371371
Alcotest.(check int __LOC__ (List.length multi_files) (List.length diffs));
372372
Alcotest.(check int __LOC__ (List.length multi_exp) (List.length diffs));
373373
List.iter2 (fun diff (input, expected) ->
@@ -412,11 +412,11 @@ let read file =
412412

413413
let opt_read file = try Some (read file) with Unix.Unix_error _ -> None
414414

415-
let op_test = Alcotest.testable (Patch.pp_operation ~git:false) Patch.operation_eq
415+
let op_test = Alcotest.testable Patch.pp_operation Patch.operation_eq
416416

417417
let parse_real_diff_header file hdr () =
418418
let data = read (file ^ ".diff") in
419-
let diffs = Patch.parse data in
419+
let diffs = Patch.parse ~p:0 data in
420420
Alcotest.(check int __LOC__ 1 (List.length diffs));
421421
Alcotest.check op_test __LOC__ hdr (List.hd diffs).Patch.operation
422422

@@ -425,17 +425,17 @@ let parse_real_diff_headers =
425425
"parsing " ^ file ^ ".diff", `Quick, parse_real_diff_header file hdr)
426426
[ "first", Patch.Edit ("first.old", "first.new") ;
427427
"create1", Patch.Create "a/create1" ;
428-
"git1", Patch.Create "git1.new" ;
428+
"git1", Patch.Create "b/git1.new" ;
429429
"git2", Patch.Rename_only ("git2.old", "git2.new") ;
430-
"git3", Patch.Edit ("git3.old", "git3.new") ;
431-
"git4", Patch.Delete "git4.old"
430+
"git3", Patch.Edit ("a/git3.old", "b/git3.new") ;
431+
"git4", Patch.Delete "a/git4.old"
432432
]
433433

434434
let regression_test name () =
435435
let old = opt_read (name ^ ".old") in
436436
let diff = read (name ^ ".diff") in
437437
let exp = opt_read (name ^ ".new") in
438-
match Patch.parse diff with
438+
match Patch.parse ~p:0 diff with
439439
| [ diff ] ->
440440
let res = Patch.patch old diff in
441441
Alcotest.(check (option string) __LOC__ exp res)
@@ -807,7 +807,7 @@ let unified_diff_creation = [
807807
]
808808

809809
let operations exp diff () =
810-
let ops = diff |> Patch.parse |> List.map (fun p -> p.Patch.operation) in
810+
let ops = diff |> Patch.parse ~p:0 |> List.map (fun p -> p.Patch.operation) in
811811
Alcotest.(check (list op_test)) __LOC__ exp ops
812812

813813
let unified_diff_spaces = {|\
@@ -832,7 +832,7 @@ index ef00db3..88adca3 100644
832832
|}
833833

834834
let git_diff_spaces =
835-
operations [Patch.Edit ("foo bar", "foo bar")] git_diff_spaces
835+
operations [Patch.Edit ("a/foo bar", "b/foo bar")] git_diff_spaces
836836

837837
let busybox_diff_spaces = {|\
838838
--- a/foo bar
@@ -867,7 +867,7 @@ index 88adca3..ef00db3 100644
867867
|}
868868

869869
let git_diff_quotes =
870-
operations [Patch.Edit ({|foo bar "baz"|}, {|"foo" bar baz|})] git_diff_quotes
870+
operations [Patch.Edit ({|a/foo bar "baz"|}, {|b/"foo" bar baz|})] git_diff_quotes
871871

872872
let busybox_diff_quotes = {|\
873873
--- foo bar "baz"
@@ -970,6 +970,47 @@ let filename_diffs =
970970
"unquoted filename with backslashes", `Quick, unquoted_filename;
971971
]
972972
973+
let operations ~p exp diff () =
974+
let ops = diff |> Patch.parse ~p |> List.map (fun p -> p.Patch.operation) in
975+
Alcotest.(check (list op_test)) __LOC__ exp ops
976+
977+
let p1_p2 = {|\
978+
--- a.orig/a/test
979+
+++ b.new/b/test
980+
@@ -0,0 +1 @@
981+
+aaa
982+
|}
983+
984+
let p1 = operations ~p:1 [Patch.Edit ("a/test", "b/test")] p1_p2
985+
let p2 = operations ~p:2 [Patch.Edit ("test", "test")] p1_p2
986+
987+
let p1_adjacent_slashes = {|\
988+
--- a///some//dir////test
989+
+++ b///some/dir/test
990+
@@ -0,0 +1 @@
991+
+aaa
992+
|}
993+
994+
let p1_adjacent_slashes = operations ~p:1 [Patch.Edit ("some/dir/test", "some/dir/test")] p1_adjacent_slashes
995+
996+
let p0_p1_root = {|\
997+
--- /a/test
998+
+++ /b/test
999+
@@ -0,0 +1 @@
1000+
+aaa
1001+
|}
1002+
1003+
let p0_root = operations ~p:0 [Patch.Edit ("/a/test", "/b/test")] p0_p1_root
1004+
let p1_root = operations ~p:1 [Patch.Edit ("a/test", "b/test")] p0_p1_root
1005+
1006+
let patch_p = [
1007+
"-p1", `Quick, p1;
1008+
"-p2", `Quick, p2;
1009+
"-p1 with adjacent slashes", `Quick, p1_adjacent_slashes;
1010+
"-p0 with root files", `Quick, p0_root;
1011+
"-p1 with root files", `Quick, p1_root;
1012+
]
1013+
9731014
let tests = [
9741015
"parse", parse_diffs ;
9751016
"apply", apply_diffs ;
@@ -979,6 +1020,7 @@ let tests = [
9791020
"parse real diffs", parse_real_diff_headers ;
9801021
"regression", regression_diffs ;
9811022
"diff", unified_diff_creation ;
1023+
"patch -p", patch_p;
9821024
]
9831025
9841026
let () =

0 commit comments

Comments
 (0)