1
-
Removed:
(* -*- mode: tuareg; -*- *)
2
-
Removed:
3
-
Removed:
(* -*- mode: tuareg; -*- *)
4
-
Removed:
5
-
Removed:
(** Line diffs between two blobs.
6
-
Removed:
7
-
Removed:
Computes a longest-common-subsequence diff and groups the result into hunks
8
-
Removed:
with the surrounding context, which is the shape a reader expects from
9
-
Removed:
[git diff]. Large inputs fall back to a coarser result rather than spending
10
-
Removed:
unbounded time and memory on the LCS matrix.
11
-
Removed:
12
-
Removed:
This module produces data only. Rendering it is {!module:Ui.Diff}'s job.
13
-
Removed:
14
-
Removed:
TODO: document the exact size threshold and the shape of the fallback, once
15
-
Removed:
the limit is settled. *)
16
-
Removed:
17
-
Removed:
type line_kind = Context | Addition | Deletion
18
-
Removed:
19
-
Removed:
type line = {
20
-
Removed:
kind : line_kind;
21
-
Removed:
old_number : int option;
22
-
Removed:
new_number : int option;
23
-
Removed:
text : string;
24
-
Removed:
}
25
-
Removed:
26
-
Removed:
type hunk = {
27
-
Removed:
old_start : int;
28
-
Removed:
old_count : int;
29
-
Removed:
new_start : int;
30
-
Removed:
new_count : int;
31
-
Removed:
lines : line list;
32
-
Removed:
}
33
-
Removed:
34
-
Removed:
type file = {
35
-
Removed:
path : string;
36
-
Removed:
old_hash : string option;
37
-
Removed:
new_hash : string option;
38
-
Removed:
old_mode : int option;
39
-
Removed:
new_mode : int option;
40
-
Removed:
binary : bool;
41
-
Removed:
hunks : hunk list;
42
-
Removed:
}
43
-
Removed:
44
-
Removed:
let split_lines content =
45
-
Removed:
match String.split_on_char '\n' content with
46
-
Removed:
| [] -> []
47
-
Removed:
| lines ->
48
-
Removed:
if content = "" then []
49
-
Removed:
else if String.ends_with ~suffix:"\n" content then
50
-
Removed:
List.rev lines |> List.tl |> List.rev
51
-
Removed:
else lines
52
-
Removed:
53
-
Removed:
let line_diff old_content new_content =
54
-
Removed:
let old_lines = Array.of_list (split_lines old_content) in
55
-
Removed:
let new_lines = Array.of_list (split_lines new_content) in
56
-
Removed:
let old_length = Array.length old_lines in
57
-
Removed:
let new_length = Array.length new_lines in
58
-
Removed:
let matrix_size = old_length * new_length in
59
-
Removed:
let rec all_deletions index acc =
60
-
Removed:
if index = old_length then List.rev acc
61
-
Removed:
else
62
-
Removed:
all_deletions (index + 1)
63
-
Removed:
({
64
-
Removed:
kind = Deletion;
65
-
Removed:
old_number = Some (index + 1);
66
-
Removed:
new_number = None;
67
-
Removed:
text = old_lines.(index);
68
-
Removed:
}
69
-
Removed:
:: acc)
70
-
Removed:
in
71
-
Removed:
let rec all_additions index acc =
72
-
Removed:
if index = new_length then List.rev acc
73
-
Removed:
else
74
-
Removed:
all_additions (index + 1)
75
-
Removed:
({
76
-
Removed:
kind = Addition;
77
-
Removed:
old_number = None;
78
-
Removed:
new_number = Some (index + 1);
79
-
Removed:
text = new_lines.(index);
80
-
Removed:
}
81
-
Removed:
:: acc)
82
-
Removed:
in
83
-
Removed:
if matrix_size > 4_000_000 then all_deletions 0 [] @ all_additions 0 []
84
-
Removed:
else
85
-
Removed:
let lengths = Array.make_matrix (old_length + 1) (new_length + 1) 0 in
86
-
Removed:
for old_index = old_length - 1 downto 0 do
87
-
Removed:
for new_index = new_length - 1 downto 0 do
88
-
Removed:
lengths.(old_index).(new_index) <-
89
-
Removed:
(if old_lines.(old_index) = new_lines.(new_index) then
90
-
Removed:
lengths.(old_index + 1).(new_index + 1) + 1
91
-
Removed:
else
92
-
Removed:
max
93
-
Removed:
lengths.(old_index + 1).(new_index)
94
-
Removed:
lengths.(old_index).(new_index + 1))
95
-
Removed:
done
96
-
Removed:
done;
97
-
Removed:
let rec build old_index new_index acc =
98
-
Removed:
if old_index = old_length then List.rev acc @ all_additions new_index []
99
-
Removed:
else if new_index = new_length then
100
-
Removed:
List.rev acc @ all_deletions old_index []
101
-
Removed:
else if old_lines.(old_index) = new_lines.(new_index) then
102
-
Removed:
build (old_index + 1) (new_index + 1)
103
-
Removed:
({
104
-
Removed:
kind = Context;
105
-
Removed:
old_number = Some (old_index + 1);
106
-
Removed:
new_number = Some (new_index + 1);
107
-
Removed:
text = old_lines.(old_index);
108
-
Removed:
}
109
-
Removed:
:: acc)
110
-
Removed:
else if
111
-
Removed:
lengths.(old_index + 1).(new_index)
112
-
Removed:
>= lengths.(old_index).(new_index + 1)
113
-
Removed:
then
114
-
Removed:
build (old_index + 1) new_index
115
-
Removed:
({
116
-
Removed:
kind = Deletion;
117
-
Removed:
old_number = Some (old_index + 1);
118
-
Removed:
new_number = None;
119
-
Removed:
text = old_lines.(old_index);
120
-
Removed:
}
121
-
Removed:
:: acc)
122
-
Removed:
else
123
-
Removed:
build old_index (new_index + 1)
124
-
Removed:
({
125
-
Removed:
kind = Addition;
126
-
Removed:
old_number = None;
127
-
Removed:
new_number = Some (new_index + 1);
128
-
Removed:
text = new_lines.(new_index);
129
-
Removed:
}
130
-
Removed:
:: acc)
131
-
Removed:
in
132
-
Removed:
build 0 0 []
133
-
Removed:
134
-
Removed:
let hunks ?(context = 3) lines =
135
-
Removed:
let lines = Array.of_list lines in
136
-
Removed:
let length = Array.length lines in
137
-
Removed:
let changed =
138
-
Removed:
Array.to_list (Array.mapi (fun index line -> (index, line.kind)) lines)
139
-
Removed:
|> List.filter_map (function
140
-
Removed:
| index, (Addition | Deletion) -> Some index
141
-
Removed:
| _, Context -> None)
142
-
Removed:
in
143
-
Removed:
let ranges =
144
-
Removed:
let add_range ranges index =
145
-
Removed:
let first = max 0 (index - context) in
146
-
Removed:
let last = min (length - 1) (index + context) in
147
-
Removed:
match ranges with
148
-
Removed:
| (range_first, range_last) :: rest when first <= range_last + 1 ->
149
-
Removed:
(range_first, max range_last last) :: rest
150
-
Removed:
| _ -> (first, last) :: ranges
151
-
Removed:
in
152
-
Removed:
List.fold_left add_range [] changed |> List.rev
153
-
Removed:
in
154
-
Removed:
let number_or_zero get_number slice =
155
-
Removed:
List.find_map get_number slice |> Option.value ~default:0
156
-
Removed:
in
157
-
Removed:
let make_hunk (first, last) =
158
-
Removed:
let rec slice index acc =
159
-
Removed:
if index > last then List.rev acc
160
-
Removed:
else slice (index + 1) (lines.(index) :: acc)
161
-
Removed:
in
162
-
Removed:
let lines = slice first [] in
163
-
Removed:
{
164
-
Removed:
old_start = number_or_zero (fun line -> line.old_number) lines;
165
-
Removed:
old_count =
166
-
Removed:
List.fold_left
167
-
Removed:
(fun count line ->
168
-
Removed:
if Option.is_some line.old_number then count + 1 else count)
169
-
Removed:
0 lines;
170
-
Removed:
new_start = number_or_zero (fun line -> line.new_number) lines;
171
-
Removed:
new_count =
172
-
Removed:
List.fold_left
173
-
Removed:
(fun count line ->
174
-
Removed:
if Option.is_some line.new_number then count + 1 else count)
175
-
Removed:
0 lines;
176
-
Removed:
lines;
177
-
Removed:
}
178
-
Removed:
in
179
-
Removed:
List.map make_hunk ranges