1use std::{
2 io::{self, BufRead, BufReader, Read, Write},
3 ops::{Deref, Range, RangeInclusive},
4 sync::Arc,
5};
6
7use anyhow::{Context, Result, ensure};
8
9use crate::{
10 model::{Document as Parsed, Kind, Row, RowKind},
11 projection::{Projection, Rows},
12 source_spans::SourceSpans,
13};
14
15mod metadata;
16pub use metadata::{FileMetadata, HunkMetadata};
17
18mod bytes;
19pub(crate) use bytes::private_tempfile;
20pub use bytes::{ByteStore, SourceRef, StoreReader, StoreWriter};
21mod reader;
22pub(crate) use reader::ReadWindow;
23
24#[cfg(test)]
25pub(crate) mod tests;
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29pub struct SourceId {
30 store: bytes::StoreId,
31 record: usize,
32}
33
34struct Spooling<R> {
35 input: R,
36 writer: StoreWriter,
37}
38impl<R: Read> Read for Spooling<R> {
39 fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
40 let n = self.input.read(out)?;
41 self.writer.append(&out[..n]).map_err(io::Error::other)?;
42 Ok(n)
43 }
44}
45
46#[derive(Clone, Copy, Debug)]
47pub struct LineInfo {
48 pub kind: Kind,
49 pub old: Option<u32>,
50 pub new: Option<u32>,
51 pub no_newline: bool,
52}
53
54#[derive(Debug)]
55pub struct FileIndex {
56 pub id: usize,
57 pub source_base: usize,
58 pub split_base: usize,
59 pub unified_base: usize,
60 pub meta: FileMetadata,
61 pub spool: Arc<ByteStore>,
62 source: SourceSpans,
63 projection: Projection,
64}
65
66impl Deref for FileIndex {
67 type Target = FileMetadata;
68 fn deref(&self) -> &Self::Target {
69 &self.meta
70 }
71}
72
73impl FileIndex {
74 pub(crate) fn hunk_row(&self, index: usize, split: bool) -> usize {
75 self.projection.hunk_row(index, split)
76 }
77 pub fn source_end(&self) -> usize {
78 self.source_base + self.source.len()
79 }
80 pub fn row_base(&self, split: bool) -> usize {
81 if split {
82 self.split_base
83 } else {
84 self.unified_base
85 }
86 }
87 pub fn rows(&self, split: bool) -> Rows<'_> {
88 self.projection.rows(split, self.id, self.source_base)
89 }
90 pub fn row_end(&self, split: bool) -> usize {
91 self.row_base(split) + self.rows(split).len()
92 }
93 pub fn row_for_source(&self, id: usize, split: bool) -> usize {
94 self.row_base(split) + self.projection.row_for_source(id - self.source_base, split)
95 }
96 pub fn info(&self, id: usize) -> LineInfo {
97 let n = id - self.source_base;
98 let (kind, old, new) = self.projection.info(n);
99 LineInfo {
100 kind,
101 old,
102 new,
103 no_newline: self.source.no_newline(n),
104 }
105 }
106 pub fn bytes(&self, id: usize) -> Result<Vec<u8>> {
107 self.spool.read_source(self.source_ref(id)?)
108 }
109 pub fn source_id(&self, id: usize) -> Result<SourceId> {
110 ensure!(
111 (self.source_base..self.source_end()).contains(&id),
112 "Source line is outside this file"
113 );
114 Ok(SourceId {
115 store: self.spool.id(),
116 record: id,
117 })
118 }
119 pub fn source_ref(&self, id: usize) -> Result<SourceRef> {
120 self.source_id(id)?;
121 let (offset, len) = self.source.range(id - self.source_base)?;
122 self.spool.source_ref(offset, len)
123 }
124 pub(crate) fn source_refs(
125 &self,
126 range: Range<usize>,
127 ) -> Result<impl Iterator<Item = Result<SourceRef>> + '_> {
128 ensure!(
129 range.start >= self.source_base
130 && range.start <= range.end
131 && range.end <= self.source_end(),
132 "Source range is outside this file"
133 );
134 Ok(self
135 .source
136 .cursor(range.start - self.source_base..range.end - self.source_base)?
137 .map(|span| {
138 let (offset, len) = span?;
139 self.spool.source_ref(offset, len)
140 }))
141 }
142 pub fn partner(&self, id: usize) -> Option<usize> {
143 let row = self
144 .rows(true)
145 .get(self.row_for_source(id, true) - self.split_base)?;
146 let RowKind::Code {
147 left: Some(left),
148 right: Some(right),
149 } = row.kind
150 else {
151 return None;
152 };
153 if left == right {
154 None
155 } else {
156 Some(if id == left { right } else { left })
157 }
158 }
159 pub fn source_index_bytes(&self) -> usize {
160 self.source.bytes()
161 }
162 pub fn source_index_stored_bytes(&self) -> u64 {
163 self.source.stored_bytes()
164 }
165 pub fn projection_bytes(&self) -> usize {
166 self.projection.bytes()
167 }
168 pub fn source_run_bytes(&self) -> usize {
169 self.projection.source_run_bytes()
170 }
171 pub fn shared_block_bytes(&self) -> usize {
172 self.projection.block_bytes()
173 }
174 pub fn side_index_bytes(&self) -> usize {
175 self.projection.side_index_bytes()
176 }
177 pub fn marker_index_bytes(&self) -> usize {
178 self.projection.marker_bytes()
179 }
180 pub fn boundary_index_bytes(&self) -> usize {
181 self.projection.boundary_bytes()
182 }
183 pub fn metadata_bytes(&self) -> usize {
184 self.raw_path.len()
185 + self.meta.hunks.len() * size_of::<HunkMetadata>()
186 + self
187 .meta
188 .hunks
189 .iter()
190 .map(|h| h.header.len())
191 .sum::<usize>()
192 + [
193 &self.path,
194 &self.old_path,
195 &self.status,
196 &self.old_mode,
197 &self.new_mode,
198 ]
199 .iter()
200 .map(|s| s.len())
201 .sum::<usize>()
202 }
203
204 fn selected_intervals<'a>(
205 &'a self,
206 old: bool,
207 lines: RangeInclusive<u32>,
208 check: &'a impl Fn() -> Result<()>,
209 ) -> impl Iterator<Item = Result<(Range<usize>, u64)>> + 'a {
210 let first = u64::from(*lines.start());
211 let limit = u64::from(*lines.end()) + 1;
212 self.projection
213 .source_intervals(old)
214 .filter_map(move |(ids, line)| {
215 if let Err(error) = check() {
216 return Some(Err(error));
217 }
218 let line = u64::from(line?);
219 let start = first.max(line);
220 let end = limit.min(line + ids.len() as u64);
221 (start < end).then(|| {
222 let base = self.source_base + ids.start;
223 Ok((
224 base + (start - line) as usize..base + (end - line) as usize,
225 start,
226 ))
227 })
228 })
229 }
230
231 pub(crate) fn write_source_lines(
232 &self,
233 old: bool,
234 lines: RangeInclusive<u32>,
235 output: &mut impl Write,
236 check: &impl Fn() -> Result<()>,
237 ) -> Result<u64> {
238 ensure!(
239 *lines.start() != 0 && lines.start() <= lines.end(),
240 "Invalid source line range"
241 );
242 let limit = u64::from(*lines.end()) + 1;
243 let mut next = u64::from(*lines.start());
244 let mut bounds: Option<Range<usize>> = None;
245 for interval in self.selected_intervals(old, lines.clone(), check) {
246 let (ids, line) = interval?;
247 check()?;
248 ensure!(
249 line == next,
250 "Requested source range contains lines missing from the patch"
251 );
252 next += ids.len() as u64;
253 if let Some(bounds) = &mut bounds {
254 bounds.end = ids.end;
255 } else {
256 bounds = Some(ids);
257 }
258 if next == limit {
259 break;
260 }
261 }
262 check()?;
263 ensure!(
264 next == limit,
265 "Requested source range contains lines missing from the patch"
266 );
267 let bounds = bounds.context("The requested side has no supplied source lines")?;
268 let first = bounds.start - self.source_base;
269 let last = bounds.end - self.source_base - 1;
270 let (offset, _) = self
271 .source
272 .cursor(first..first + 1)?
273 .next()
274 .context("Missing first source span")??;
275 let (end, len) = self
276 .source
277 .cursor(last..last + 1)?
278 .next()
279 .context("Missing last source span")??;
280 let end = end
281 .checked_add(len as u64)
282 .context("Source span overflow")?;
283 let len = end
284 .checked_sub(offset)
285 .context("Source spans are out of order")?;
286 let mut body = BufReader::with_capacity(64 * 1024, self.spool.reader(offset, len)?);
287 let mut position = offset;
288 let mut written = 0u64;
289 let mut intervals = self.selected_intervals(old, lines, check);
290 let mut selected = intervals
291 .next()
292 .context("Missing selected source interval")??
293 .0;
294 let spans = self.source.cursor(first..last + 1)?;
295 for (id, span) in bounds.zip(spans) {
296 check()?;
297 let (offset, len) = span?;
298 if id >= selected.end {
299 selected = intervals
300 .next()
301 .context("Missing selected source interval")??
302 .0;
303 }
304 if id < selected.start {
305 continue;
306 }
307 let skip = offset
308 .checked_sub(position)
309 .context("Source spans overlap or are out of order")?;
310 if skip > body.buffer().len() as u64 {
311 let remaining = end
312 .checked_sub(offset)
313 .context("Source span exceeds selected range")?;
314 body.consume(body.buffer().len());
315 *body.get_mut() = self.spool.reader(offset, remaining)?;
316 } else {
317 body.consume(skip as usize);
318 }
319 copy_checked(&mut body, output, len as u64, check)?;
320 position = offset
321 .checked_add(len as u64)
322 .context("Source span overflow")?;
323 written = written
324 .checked_add(len as u64)
325 .context("Source output length overflow")?;
326 }
327 check()?;
328 Ok(written)
329 }
330
331 pub fn write_canonical(&self, output: &mut impl Write) -> Result<()> {
332 output.write_all(b"F")?;
333 output.write_all(&(self.id as u64).to_le_bytes())?;
334 let mut body = None;
335 let mut position = 0;
336 for hunk in &self.hunks {
337 output.write_all(b"H")?;
338 output.write_all(&u64::from(hunk.old_start).to_le_bytes())?;
339 output.write_all(&u64::from(hunk.new_start).to_le_bytes())?;
340 let spans = self
341 .source
342 .cursor(hunk.lines.start - self.source_base..hunk.lines.end - self.source_base)?;
343 for (id, span) in hunk.lines.clone().zip(spans) {
344 let info = self.info(id);
345 let sign = match info.kind {
346 Kind::Context => b' ',
347 Kind::Addition => b'+',
348 Kind::Deletion => b'-',
349 };
350 let (offset, len) = span?;
351 if body.is_none() {
352 let last = self.source.len() - 1;
353 let (end, len) = self
354 .source
355 .cursor(last..last + 1)?
356 .next()
357 .context("Missing final source span")??;
358 let length = (end + len as u64)
359 .checked_sub(offset)
360 .context("Patch source spans are out of order")?;
361 body = Some(BufReader::with_capacity(
362 64 * 1024,
363 self.spool.reader(offset, length)?,
364 ));
365 position = offset;
366 }
367 let body = body.as_mut().context("Missing canonical source reader")?;
368 let skip = offset
369 .checked_sub(position)
370 .context("Patch source spans overlap or are out of order")?;
371 copy_exact(body, &mut io::sink(), skip)
372 .context("Cannot advance to the next patch source span")?;
373 output.write_all(&[b'L', sign])?;
374 output.write_all(&u64::from(info.old.unwrap_or(0)).to_le_bytes())?;
375 output.write_all(&u64::from(info.new.unwrap_or(0)).to_le_bytes())?;
376 output.write_all(&[u8::from(info.no_newline)])?;
377 output.write_all(&(len as u64).to_le_bytes())?;
378 copy_exact(body, output, len as u64)
379 .context("Cannot copy the patch source span")?;
380 position = offset + len as u64;
381 }
382 }
383 Ok(())
384 }
385}
386
387fn copy_exact(input: &mut impl BufRead, output: &mut impl Write, len: u64) -> Result<()> {
388 copy_checked(input, output, len, &|| Ok(()))
389}
390
391pub(crate) fn copy_checked(
392 input: &mut impl BufRead,
393 output: &mut impl Write,
394 mut len: u64,
395 check: &impl Fn() -> Result<()>,
396) -> Result<()> {
397 while len != 0 {
398 check()?;
399 let bytes = match input.fill_buf() {
400 Ok(bytes) => bytes,
401 Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
402 Err(error) => return Err(error.into()),
403 };
404 if bytes.is_empty() {
405 return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into());
406 }
407 let count = len.min(bytes.len() as u64) as usize;
408 output.write_all(&bytes[..count])?;
409 input.consume(count);
410 len -= count as u64;
411 }
412 Ok(())
413}
414
415#[derive(Default, Debug)]
416pub struct Document {
417 pub files: Vec<Arc<FileIndex>>,
418}
419
420impl Document {
421 pub fn append(&mut self, file: Arc<FileIndex>) -> Result<()> {
422 ensure!(
423 file.id == self.files.len()
424 && file.source_base == self.source_len()
425 && file.split_base == self.row_len(true)
426 && file.unified_base == self.row_len(false),
427 "Input worker published files out of order"
428 );
429 self.files.push(file);
430 Ok(())
431 }
432 pub fn source_len(&self) -> usize {
433 self.files.last().map_or(0, |f| f.source_end())
434 }
435 pub fn row_len(&self, split: bool) -> usize {
436 self.files.last().map_or(0, |f| f.row_end(split))
437 }
438 pub fn file_for_source(&self, id: usize) -> &Arc<FileIndex> {
439 &self.files[self.files.partition_point(|f| f.source_end() <= id)]
440 }
441 pub fn info(&self, id: usize) -> LineInfo {
442 self.file_for_source(id).info(id)
443 }
444 pub fn bytes(&self, id: usize) -> Result<Vec<u8>> {
445 self.file_for_source(id).bytes(id)
446 }
447 pub fn row(&self, index: usize, split: bool) -> Option<Row> {
448 let f = self
449 .files
450 .get(self.files.partition_point(|f| f.row_end(split) <= index))?;
451 f.rows(split).get(index - f.row_base(split))
452 }
453 pub fn row_for_source(&self, id: usize, split: bool) -> usize {
454 self.file_for_source(id).row_for_source(id, split)
455 }
456 pub fn boundary(&self, row: usize, split: bool, forward: bool, file: bool) -> Option<usize> {
457 let current = self.files.partition_point(|f| f.row_end(split) <= row);
458 let f = self.files.get(current)?;
459 if let Some(target) = f
460 .rows(split)
461 .boundary(row - f.row_base(split), forward, file)
462 {
463 return Some(f.row_base(split) + target);
464 }
465 if forward {
466 self.files[current + 1..].iter().find_map(|f| {
467 f.rows(split)
468 .first_boundary(true, file)
469 .map(|r| f.row_base(split) + r)
470 })
471 } else {
472 self.files[..current].iter().rev().find_map(|f| {
473 f.rows(split)
474 .first_boundary(false, file)
475 .map(|r| f.row_base(split) + r)
476 })
477 }
478 }
479}
480
481pub fn read(
482 input: impl Read,
483 mut publish: impl FnMut(Arc<FileIndex>) -> Result<()>,
484) -> Result<Arc<ByteStore>> {
485 let writer = StoreWriter::new()?;
486 read_with(input, writer, &|| Ok(()), |file, _| publish(file))
487}
488
489pub(crate) fn read_with(
490 input: impl Read,
491 writer: StoreWriter,
492 check: &impl Fn() -> Result<()>,
493 mut publish: impl FnMut(Arc<FileIndex>, crate::model::PatchIdentity) -> Result<()>,
494) -> Result<Arc<ByteStore>> {
495 let spool = writer.store();
496 let mut index = StoreWriter::sharing(&spool)?;
497 let input = io::BufReader::new(Spooling { input, writer });
498 let (mut file_id, mut source_base, mut split_base, mut unified_base) = (0, 0, 0, 0);
499 let total = crate::ingest::visit_files(input, |unit_base, unit| {
502 check()?;
503 let mut parsed = Parsed::parse_file(unit)?;
504 for mut meta in std::mem::take(&mut parsed.files) {
505 let identity = std::mem::take(&mut meta.identity);
506 let projection = Projection::new(&parsed, &meta);
507 let range = meta.hunks.first().map_or(0, |h| h.lines.start)
508 ..meta.hunks.last().map_or(0, |h| h.lines.end);
509 let source = SourceSpans::new(
510 &mut index,
511 unit_base,
512 parsed.lines[range.clone()]
513 .iter()
514 .map(|line| (line.offset as u64, line.bytes.len(), line.no_newline)),
515 spool.len(),
516 check,
517 )?;
518 for hunk in &mut meta.hunks {
519 hunk.lines = (hunk.lines.start - range.start + source_base)
520 ..(hunk.lines.end - range.start + source_base);
521 }
522 let file = Arc::new(FileIndex {
523 id: file_id,
524 source_base,
525 split_base,
526 unified_base,
527 meta: meta.into(),
528 spool: spool.clone(),
529 source,
530 projection,
531 });
532 source_base = file.source_end();
533 split_base = file.row_end(true);
534 unified_base = file.row_end(false);
535 file_id += 1;
536 publish(file, identity)?;
537 }
538 Ok(())
539 })?;
540 ensure!(
541 total == spool.len(),
542 "Spool and parsed patch lengths disagree"
543 );
544 Ok(spool)
545}