Skip to main content

diffs_rs/
storage.rs

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/// An original patch record, independent of its current screen row.
28#[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    // Text between file patches, such as commit headers, is skipped. Each unit carries
500    // its own offset in the original stream, which the spool retains unchanged.
501    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}