Skip to content

Commit 7db379a

Browse files
committed
Add support for context diffs
1 parent 53b3d21 commit 7db379a

File tree

1 file changed

+62
-39
lines changed

1 file changed

+62
-39
lines changed

src/patch.ml

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ type parse_error = {
1515
(* TODO: add the start position of the error *)
1616
}
1717

18+
type diff_kind =
19+
| Unified_diff
20+
| Context_diff
21+
1822
exception Parse_error of parse_error
1923

2024
let unified_diff ~mine_no_nl ~their_no_nl hunk =
@@ -129,21 +133,25 @@ let to_start_len data =
129133
(* TODO: investigate start line. This shouldn't be start - 1 but the output of diff is also inconsistent *)
130134
(st, len)
131135

132-
let count_to_sl_sl data =
133-
if String.is_prefix ~prefix:"@@ -" data then
134-
(* input: "@@ -19,23 +19,12 @@ bla" *)
135-
(* output: ((19,23), (19, 12)) *)
136-
match List.filter (function "" -> false | _ -> true) (String.cuts '@' data) with
137-
| numbers::_ ->
138-
let nums = String.trim numbers in
139-
(match String.cut ' ' nums with
140-
| None -> invalid_arg "couldn't find space in count"
141-
| Some (mine, theirs) -> Some (to_start_len mine, to_start_len theirs))
142-
| _ -> invalid_arg "broken line!"
143-
else
144-
None
136+
let count_to_sl_sl diff_kind data =
137+
match diff_kind with
138+
| Unified_diff ->
139+
if String.is_prefix ~prefix:"@@ -" data then
140+
(* input: "@@ -19,23 +19,12 @@ bla" *)
141+
(* output: ((19,23), (19, 12)) *)
142+
match List.filter (function "" -> false | _ -> true) (String.cuts '@' data) with
143+
| numbers::_ ->
144+
let nums = String.trim numbers in
145+
(match String.cut ' ' nums with
146+
| None -> invalid_arg "couldn't find space in count"
147+
| Some (mine, theirs) -> Some (to_start_len mine, to_start_len theirs))
148+
| _ -> invalid_arg "broken line!"
149+
else
150+
None
151+
| Context_diff ->
152+
assert false
145153

146-
let sort_into_bags ~counter:(mine_len, their_len) dir mine their m_nl t_nl str =
154+
let sort_into_bags diff_kind ~counter:(mine_len, their_len) dir mine their m_nl t_nl str =
147155
let both data =
148156
if m_nl || t_nl then
149157
failwith "\"no newline at the end of file\" is not at the end of the file";
@@ -157,26 +165,36 @@ let sort_into_bags ~counter:(mine_len, their_len) dir mine their m_nl t_nl str =
157165
None
158166
else if str_len = 0 then
159167
both "" (* NOTE: this should technically be a parse error but GNU patch accepts that and some patches in opam-repository do use this behaviour *)
160-
else match String.get str 0, String.slice ~start:1 str with
161-
| ' ', data ->
168+
else
169+
let data =
170+
let start = match diff_kind with
171+
| Unified_diff -> 1
172+
| Context_diff when str_len = 1 -> failwith "bad context diff"
173+
| Context_diff when str.[1] = ' ' || str.[1] = '\t' -> 2
174+
| Context_diff -> 1
175+
in
176+
String.slice ~start str
177+
in
178+
match String.get str 0, diff_kind with
179+
| ' ', _ ->
162180
both data
163-
| '\t', data ->
181+
| '\t', _ ->
164182
both ("\t"^data) (* NOTE: not valid but accepted by GNU patch *)
165-
| '+', data ->
183+
| '+', _ ->
166184
if t_nl then
167185
failwith "\"no newline at the end of file\" is not at the end of the file";
168186
if their_len = 0 then
169187
failwith "invalid patch (+ size exhausted)";
170188
let counter = (mine_len, their_len - 1) in
171189
Some (counter, `Their, mine, (data :: their), m_nl, t_nl)
172-
| '-', data ->
190+
| '-', _ | '!', Context_diff ->
173191
if m_nl then
174192
failwith "\"no newline at the end of file\" is not at the end of the file";
175193
if mine_len = 0 then
176194
failwith "invalid patch (- size exhausted)";
177195
let counter = (mine_len - 1, their_len) in
178196
Some (counter, `Mine, (data :: mine), their, m_nl, t_nl)
179-
| '\\', _data ->
197+
| '\\', _ ->
180198
(* NOTE: Any line starting with '\' is taken as if it was
181199
'\ No newline at end of file' by GNU patch so we do the same *)
182200
(* diff: 'No newline at end of file' turns out to be context-sensitive *)
@@ -189,10 +207,10 @@ let sort_into_bags ~counter:(mine_len, their_len) dir mine their m_nl t_nl str =
189207
in
190208
let counter = (mine_len, their_len) in
191209
Some (counter, dir, mine, their, my_nl, their_nl)
192-
| _ -> failwith "invalid patch (unknown character)"
210+
| _, _ -> failwith "invalid patch (unknown character)"
193211

194-
let to_hunk count data mine_no_nl their_no_nl =
195-
match count_to_sl_sl count with
212+
let to_hunk diff_kind count data mine_no_nl their_no_nl =
213+
match count_to_sl_sl diff_kind count with
196214
| None -> None, mine_no_nl, their_no_nl, count :: data
197215
| Some ((mine_start, mine_len), (their_start, their_len)) ->
198216
let counter = (mine_len, their_len) in
@@ -202,18 +220,22 @@ let to_hunk count data mine_no_nl their_no_nl =
202220
| [""] when counter = (2, 2) -> (List.rev ("" :: "" :: mine), List.rev ("" :: "" :: their), mine_no_nl, their_no_nl, []) (* GNU patch behaviour *)
203221
| [""] when counter = (3, 3) -> (List.rev ("" :: "" :: "" :: mine), List.rev ("" :: "" :: "" :: their), mine_no_nl, their_no_nl, []) (* GNU patch behaviour *)
204222
| [] | [""] -> failwith "bad file"
205-
| x::xs -> match sort_into_bags ~counter dir mine their mine_no_nl their_no_nl x with
223+
| x::xs -> match sort_into_bags diff_kind ~counter dir mine their mine_no_nl their_no_nl x with
206224
| Some (counter, dir, mine, their, mine_no_nl', their_no_nl') -> step ~counter dir mine their mine_no_nl' their_no_nl' xs
207225
| None -> (List.rev mine, List.rev their, mine_no_nl, their_no_nl, x :: xs)
208226
in
209227
let mine, their, mine_no_nl, their_no_nl, rest = step ~counter `Both [] [] mine_no_nl their_no_nl data in
210228
(Some { mine_start ; mine_len ; mine ; their_start ; their_len ; their }, mine_no_nl, their_no_nl, rest)
211229

212-
let rec to_hunks (mine_no_nl, their_no_nl, acc) = function
213-
| [] -> (List.rev acc, mine_no_nl, their_no_nl, [])
214-
| count::data -> match to_hunk count data mine_no_nl their_no_nl with
215-
| None, mine_no_nl, their_no_nl, rest -> List.rev acc, mine_no_nl, their_no_nl, rest
216-
| Some hunk, mine_no_nl, their_no_nl, rest -> to_hunks (mine_no_nl, their_no_nl, hunk :: acc) rest
230+
let rec to_hunks diff_kind (mine_no_nl, their_no_nl, acc) l = match diff_kind, l with
231+
| _, [] -> (List.rev acc, mine_no_nl, their_no_nl, [])
232+
| Unified_diff, count::data | Context_diff, "********"::count::data ->
233+
begin match to_hunk diff_kind count data mine_no_nl their_no_nl with
234+
| None, mine_no_nl, their_no_nl, rest -> List.rev acc, mine_no_nl, their_no_nl, rest
235+
| Some hunk, mine_no_nl, their_no_nl, rest -> to_hunks diff_kind (mine_no_nl, their_no_nl, hunk :: acc) rest
236+
end
237+
| Context_diff, _::xs ->
238+
to_hunks diff_kind (mine_no_nl, their_no_nl, acc) xs
217239

218240
type operation =
219241
| Edit of string * string
@@ -348,13 +370,13 @@ let parse_one ~p data =
348370
in
349371
(* first locate --- and +++ lines *)
350372
let rec find_start ~mode ~git_action = function
351-
| [] -> git_action, []
373+
| [] -> git_action, Unified_diff, []
352374
| x::xs when String.is_prefix ~prefix:"diff --git " x ->
353375
let git_filename = Fname.parse_git_header (String.slice ~start:11 x) in
354376
begin match mode, git_action with
355377
| (None | Some (Git _)), None -> find_start ~mode:(Some (Git git_filename)) ~git_action:None xs
356378
| None, Some _ -> assert false (* impossible state *)
357-
| Some (Git _), Some git_action -> (Some git_action, x :: xs)
379+
| Some (Git _), Some git_action -> (Some git_action, Unified_diff, x :: xs)
358380
end
359381
| x::y::xs when is_git mode && String.is_prefix ~prefix:"rename from " x && String.is_prefix ~prefix:"rename to " y ->
360382
let git_action = Some (Rename_only (String.slice ~start:12 x, String.slice ~start:10 y)) in
@@ -367,20 +389,21 @@ let parse_one ~p data =
367389
in
368390
find_start ~mode ~git_action xs
369391
| x::y::xs when String.is_prefix ~prefix:"--- " x && String.is_prefix ~prefix:"+++ " y ->
370-
Some (operation_of_strings ~p x y), xs
371-
| x::y::_xs when String.is_prefix ~prefix:"*** " x && String.is_prefix ~prefix:"--- " y ->
372-
failwith "Context diffs are not supported"
392+
Some (operation_of_strings ~p x y), Unified_diff, xs
393+
| x::y::"********"::xs when String.is_prefix ~prefix:"*** " x && String.is_prefix ~prefix:"--- " y ->
394+
Some (operation_of_strings ~p x y), Context_diff, xs
373395
| _::xs -> find_start ~mode ~git_action xs
374396
in
375397
match find_start ~mode:None ~git_action:None data with
376-
| Some (Rename_only _ as operation), rest ->
398+
| Some (Rename_only _ as operation), _, rest ->
399+
(* assert diff_kind = Unified_diff *)
377400
let hunks = [] and mine_no_nl = false and their_no_nl = false in
378401
Some ({ operation ; hunks ; mine_no_nl ; their_no_nl }, rest)
379-
| Some operation, rest ->
380-
let hunks, mine_no_nl, their_no_nl, rest = to_hunks (false, false, []) rest in
402+
| Some operation, diff_kind, rest ->
403+
let hunks, mine_no_nl, their_no_nl, rest = to_hunks diff_kind (false, false, []) rest in
381404
Some ({ operation ; hunks ; mine_no_nl ; their_no_nl }, rest)
382-
| None, [] -> None
383-
| None, _ -> assert false
405+
| None, _, [] -> None
406+
| None, _, _ -> assert false
384407

385408
let to_lines = String.cuts '\n'
386409

0 commit comments

Comments
 (0)