1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
//! What:
//! A repository path as an event reports it:
//! readable text,
//! and the exact bytes
//! as well when those bytes are not UTF-8.
//! Why:
//! Git allows any bytes in a file name,
//! and a JSON string can hold only text.
//! An
//! event keeps `path` as text with replacement characters,
//! so every existing reader
//! works unchanged,
//! and adds an optional `pathBytes` field,
//! base64 encoded,
//! only for
//! a name that is not UTF-8,
//! so a reader that needs the file can still find it.
//! This is option C of `doc/planning/cli-git-rust-open-decisions.md`,
//! section
//! "Non-UTF-8 paths in events and journals",
//! which the owner adopted.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! // const path = eventPath(bytes); line.path = path.text; if (path.exact) line.pathBytes = base64(path.exact);
//! ```
/// What:
/// The 64 characters of the standard base64 alphabet,
/// in value order.
/// `&[u8; 64]` is a fixed-length byte array baked into the program.
/// Why:
/// RFC 4648 section 4 is the alphabet every common decoder reads by default
/// (`Buffer.from(text, 'base64')`,
/// Python's `base64.b64decode`,
/// Go's `[]byte` JSON).
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
/// ```
const BASE64_ALPHABET: & =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/// What:
/// One path as events report it.
/// The fields are private,
/// so the only ways to build
/// one are the two constructors,
/// and `exact` is present exactly when the bytes
/// were not UTF-8.
/// `#[derive(...)]` generates cloning,
/// debug printing and `==`.
/// Why:
/// A reader must be able to rely on "no `pathBytes` means `path` is the exact name".
///
/// In TS you'd write (pseudocode):
/// ```ts
/// type EventPath = { readonly text: string; readonly exact?: Uint8Array };
/// ```
/// What:
/// `impl EventPath { ... }` attaches the constructors and the two reads.
/// Why:
/// The invariant between the two fields is kept in one place.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// class EventPath { static fromGitBytes(bytes) {} static display(text) {} }
/// ```
/// What:
/// The alphabet character for the low six bits of `value`.
/// `u32` is an unsigned
/// 32-bit integer;
/// `char::from` turns one ASCII byte into a character.
/// Why:
/// Each base64 character carries six bits of the input.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const sextet = (value: number) => ALPHABET[value & 63];
/// ```
/// What:
/// Encode bytes as standard base64 with `=` padding (RFC 4648 section 4).
/// Why:
/// `pathBytes` carries exact pathname bytes inside a JSON string.
/// Base64 is what
/// ripgrep's JSON output uses for a path that is not UTF-8 and what Go's JSON
/// encoding uses for bytes;
/// it is unmistakably not a name,
/// so no reader uses it as
/// one by accident.
/// Every three input bytes become four characters;
/// a final group
/// of one or two bytes is padded to four characters with `=`.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// function base64(bytes: Uint8Array): string { return Buffer.from(bytes).toString('base64'); }
/// ```
/// Construction,
/// invariant and encoding controls stay out of the release executable.