Skip to main content

gix_ref/store/file/log/
iter.rs

1use gix_object::bstr::ByteSlice;
2
3use crate::{
4    FullNameRef, file,
5    file::loose::reference::logiter::must_be_io_err,
6    store_impl::file::{log, log::iter::decode::LineNumber},
7};
8
9///
10pub mod decode {
11    use crate::store_impl::file::log;
12
13    /// The error returned by items in the [forward][super::forward()] and [reverse][super::reverse()] iterators
14    #[derive(Debug)]
15    pub struct Error {
16        inner: log::line::decode::Error,
17        line: LineNumber,
18    }
19
20    impl std::fmt::Display for Error {
21        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22            write!(f, "In line {}: {}", self.line, self.inner)
23        }
24    }
25
26    impl std::error::Error for Error {}
27
28    impl Error {
29        pub(crate) fn new(err: log::line::decode::Error, line: LineNumber) -> Self {
30            Error { line, inner: err }
31        }
32    }
33
34    #[derive(Debug)]
35    pub(crate) enum LineNumber {
36        FromStart(usize),
37        FromEnd(usize),
38    }
39
40    impl std::fmt::Display for LineNumber {
41        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42            let (line, suffix) = match self {
43                LineNumber::FromStart(line) => (line, ""),
44                LineNumber::FromEnd(line) => (line, " from the end"),
45            };
46            write!(f, "{}{}", line + 1, suffix)
47        }
48    }
49}
50
51/// Returns a forward iterator over the given `lines`, starting from the first line in the file and ending at the last.
52///
53/// Note that `lines` are an entire reflog file.
54///
55/// This iterator is useful when the ref log file is going to be rewritten which forces processing of the entire file.
56/// It will continue parsing even if individual log entries failed to parse, leaving it to the driver to decide whether to
57/// abort or continue.
58pub fn forward(lines: &[u8]) -> Forward<'_> {
59    Forward {
60        inner: lines.as_bstr().lines().enumerate(),
61    }
62}
63
64/// An iterator yielding parsed lines in a file from start to end, oldest to newest.
65pub struct Forward<'a> {
66    inner: std::iter::Enumerate<gix_object::bstr::Lines<'a>>,
67}
68
69impl<'a> Iterator for Forward<'a> {
70    type Item = Result<log::LineRef<'a>, decode::Error>;
71
72    fn next(&mut self) -> Option<Self::Item> {
73        self.inner.next().map(|(ln, line)| {
74            log::LineRef::from_bytes(line).map_err(|err| decode::Error::new(err, decode::LineNumber::FromStart(ln)))
75        })
76    }
77}
78
79/// A platform to store a buffer to hold ref log lines for iteration.
80#[must_use = "Iterators should be obtained from this platform"]
81pub struct Platform<'a, 's> {
82    /// The store containing the reflogs
83    pub store: &'s file::Store,
84    /// The full name of the reference whose reflog to retrieve.
85    pub name: &'a FullNameRef,
86    /// A reusable buffer for storing log lines read from disk.
87    pub buf: Vec<u8>,
88}
89
90impl Platform<'_, '_> {
91    /// Return a forward iterator over all log-lines, most recent to oldest.
92    pub fn rev(&mut self) -> std::io::Result<Option<log::iter::Reverse<'_, std::fs::File>>> {
93        self.buf.clear();
94        self.buf.resize(1024 * 4, 0);
95        self.store
96            .reflog_iter_rev(self.name, &mut self.buf)
97            .map_err(must_be_io_err)
98    }
99
100    /// Return a forward iterator over all log-lines, oldest to most recent.
101    pub fn all(&mut self) -> std::io::Result<Option<log::iter::Forward<'_>>> {
102        self.buf.clear();
103        self.store.reflog_iter(self.name, &mut self.buf).map_err(must_be_io_err)
104    }
105}
106
107/// An iterator yielding parsed lines in a file in reverse, most recent to oldest.
108pub struct Reverse<'a, F> {
109    buf: &'a mut [u8],
110    count: usize,
111    read_and_pos: Option<(F, u64)>,
112    last_nl_pos: Option<usize>,
113}
114
115/// An iterator over entries of the `log` file in reverse, using `buf` as sliding window.
116///
117/// Note that `buf` must be big enough to capture typical line length or else partial lines will be parsed and probably fail
118/// in the process.
119///
120/// This iterator is very expensive in terms of I/O operations and shouldn't be used to read more than the last few entries of the log.
121/// Use a forward iterator instead for these cases.
122///
123/// It will continue parsing even if individual log entries failed to parse, leaving it to the driver to decide whether to
124/// abort or continue.
125pub fn reverse<F>(mut log: F, buf: &mut [u8]) -> std::io::Result<Reverse<'_, F>>
126where
127    F: std::io::Read + std::io::Seek,
128{
129    let pos = log.seek(std::io::SeekFrom::End(0))?;
130    if buf.is_empty() {
131        return Err(std::io::Error::other(
132            "Zero sized buffers are not allowed, use 256 bytes or more for typical logs",
133        ));
134    }
135    Ok(Reverse {
136        buf,
137        count: 0,
138        read_and_pos: Some((log, pos)),
139        last_nl_pos: None,
140    })
141}
142
143///
144pub mod reverse {
145
146    use super::decode;
147
148    /// The error returned by the [`Reverse`][super::Reverse] iterator
149    #[derive(Debug, thiserror::Error)]
150    #[allow(missing_docs)]
151    pub enum Error {
152        #[error("The buffer could not be filled to make more lines available")]
153        Io(#[from] std::io::Error),
154        #[error("Could not decode log line")]
155        Decode(#[from] decode::Error),
156    }
157}
158
159impl<F> Iterator for Reverse<'_, F>
160where
161    F: std::io::Read + std::io::Seek,
162{
163    type Item = Result<crate::log::Line, reverse::Error>;
164
165    fn next(&mut self) -> Option<Self::Item> {
166        match (self.last_nl_pos.take(), self.read_and_pos.take()) {
167            // Initial state - load first data block
168            (None, Some((mut read, pos))) => {
169                let npos = pos.saturating_sub(self.buf.len() as u64);
170                if let Err(err) = read.seek(std::io::SeekFrom::Start(npos)) {
171                    return Some(Err(err.into()));
172                }
173
174                let n = (pos - npos) as usize;
175                if n == 0 {
176                    return None;
177                }
178                let buf = &mut self.buf[..n];
179                if let Err(err) = read.read_exact(buf) {
180                    return Some(Err(err.into()));
181                }
182
183                let last_byte = *buf.last().expect("we have read non-zero bytes before");
184                self.last_nl_pos = Some(if last_byte != b'\n' { buf.len() } else { buf.len() - 1 });
185                self.read_and_pos = Some((read, npos));
186                self.next()
187            }
188            // Has data block and can extract lines from it, load new blocks as needed
189            (Some(end), Some(read_and_pos)) => match self.buf[..end].rfind_byte(b'\n') {
190                Some(start) => {
191                    self.read_and_pos = Some(read_and_pos);
192                    self.last_nl_pos = Some(start);
193                    let buf = &self.buf[start + 1..end];
194                    let res = Some(
195                        log::LineRef::from_bytes(buf)
196                            .map_err(|err| {
197                                reverse::Error::Decode(decode::Error::new(err, LineNumber::FromEnd(self.count)))
198                            })
199                            .map(Into::into),
200                    );
201                    self.count += 1;
202                    res
203                }
204                None => {
205                    let (mut read, last_read_pos) = read_and_pos;
206                    if last_read_pos == 0 {
207                        let buf = &self.buf[..end];
208                        Some(
209                            log::LineRef::from_bytes(buf)
210                                .map_err(|err| {
211                                    reverse::Error::Decode(decode::Error::new(err, LineNumber::FromEnd(self.count)))
212                                })
213                                .map(Into::into),
214                        )
215                    } else {
216                        let npos = last_read_pos.saturating_sub((self.buf.len() - end) as u64);
217                        if npos == last_read_pos {
218                            return Some(Err(std::io::Error::other(format!(
219                                "buffer too small for line size, got until {:?}",
220                                self.buf.as_bstr()
221                            ))
222                            .into()));
223                        }
224                        let n = (last_read_pos - npos) as usize;
225                        self.buf.copy_within(0..end, n);
226                        if let Err(err) = read.seek(std::io::SeekFrom::Start(npos)) {
227                            return Some(Err(err.into()));
228                        }
229                        if let Err(err) = read.read_exact(&mut self.buf[..n]) {
230                            return Some(Err(err.into()));
231                        }
232                        self.read_and_pos = Some((read, npos));
233                        self.last_nl_pos = Some(n + end);
234                        self.next()
235                    }
236                }
237            },
238            // depleted
239            (None, None) => None,
240            (Some(_), None) => unreachable!("BUG: Invalid state: we never discard only our file, always both."),
241        }
242    }
243}