gix_revwalk/graph/
commit.rs

1use gix_date::SecondsSinceUnixEpoch;
2use smallvec::SmallVec;
3
4use super::LazyCommit;
5use crate::graph::{Commit, Either, Generation};
6
7impl<'graph, 'cache> LazyCommit<'graph, 'cache> {
8    /// Return an iterator over the parents of this commit.
9    pub fn iter_parents(&self) -> Parents<'graph, 'cache> {
10        let backing = match &self.backing {
11            Either::Left(buf) => Either::Left(gix_object::CommitRefIter::from_bytes(buf)),
12            Either::Right((cache, pos)) => Either::Right((*cache, cache.commit_at(*pos).iter_parents())),
13        };
14        Parents { backing }
15    }
16
17    /// Returns the timestamp at which this commit was created.
18    ///
19    /// This is the single-most important date for determining recency of commits.
20    /// Note that this can only fail if the commit is backed by the object database *and* parsing fails.
21    pub fn committer_timestamp(&self) -> Result<SecondsSinceUnixEpoch, gix_object::decode::Error> {
22        Ok(match &self.backing {
23            Either::Left(buf) => gix_object::CommitRefIter::from_bytes(buf).committer()?.time.seconds,
24            Either::Right((cache, pos)) => cache.commit_at(*pos).committer_timestamp() as SecondsSinceUnixEpoch, // a cast as we cannot represent the error and trying seems overkill
25        })
26    }
27
28    /// Returns the generation of the commit if it is backed by a commit graph.
29    pub fn generation(&self) -> Option<Generation> {
30        match &self.backing {
31            Either::Left(_) => None,
32            Either::Right((cache, pos)) => cache.commit_at(*pos).generation().into(),
33        }
34    }
35
36    /// Returns the generation of the commit and its commit-time, either from cache if available, or parsed from the object buffer.
37    pub fn generation_and_timestamp(
38        &self,
39    ) -> Result<(Option<Generation>, SecondsSinceUnixEpoch), gix_object::decode::Error> {
40        Ok(match &self.backing {
41            Either::Left(buf) => (
42                None,
43                gix_object::CommitRefIter::from_bytes(buf).committer()?.time.seconds,
44            ),
45            Either::Right((cache, pos)) => {
46                let commit = cache.commit_at(*pos);
47                (
48                    commit.generation().into(),
49                    // a cast as we cannot represent the error and trying seems overkill
50                    cache.commit_at(*pos).committer_timestamp() as SecondsSinceUnixEpoch,
51                )
52            }
53        })
54    }
55
56    /// Convert ourselves into an owned version, which effectively detaches us from the underlying graph.
57    /// Use `new_data()` to provide the `data` field for the owned `Commit`.
58    pub fn to_owned<T>(&self, new_data: impl FnOnce() -> T) -> Result<Commit<T>, to_owned::Error> {
59        let data = new_data();
60        Ok(match &self.backing {
61            Either::Left(buf) => {
62                use gix_object::commit::ref_iter::Token;
63                let iter = gix_object::CommitRefIter::from_bytes(buf);
64                let mut parents = SmallVec::default();
65                let mut timestamp = None;
66                for token in iter {
67                    match token? {
68                        Token::Tree { .. } => {}
69                        Token::Parent { id } => parents.push(id),
70                        Token::Author { .. } => {}
71                        Token::Committer { signature } => {
72                            timestamp = Some(signature.time.seconds);
73                            break;
74                        }
75                        _ => {
76                            unreachable!(
77                                "we break naturally after seeing the committer which is always at the same spot"
78                            )
79                        }
80                    }
81                }
82                Commit {
83                    parents,
84                    commit_time: timestamp.unwrap_or_default(),
85                    generation: None,
86                    data,
87                }
88            }
89            Either::Right((cache, pos)) => {
90                let mut parents = SmallVec::default();
91                let commit = cache.commit_at(*pos);
92                for pos in commit.iter_parents() {
93                    let pos = pos?;
94                    parents.push(cache.commit_at(pos).id().to_owned());
95                }
96                Commit {
97                    parents,
98                    commit_time: commit.committer_timestamp().try_into().map_err(|_| {
99                        to_owned::Error::CommitGraphTime {
100                            actual: commit.committer_timestamp(),
101                        }
102                    })?,
103                    generation: Some(commit.generation()),
104                    data,
105                }
106            }
107        })
108    }
109}
110
111/// An iterator over the parents of a commit.
112pub struct Parents<'graph, 'cache> {
113    backing: Either<
114        gix_object::CommitRefIter<'graph>,
115        (
116            &'cache gix_commitgraph::Graph,
117            gix_commitgraph::file::commit::Parents<'cache>,
118        ),
119    >,
120}
121
122impl Iterator for Parents<'_, '_> {
123    type Item = Result<gix_hash::ObjectId, iter_parents::Error>;
124
125    fn next(&mut self) -> Option<Self::Item> {
126        match &mut self.backing {
127            Either::Left(it) => {
128                for token in it {
129                    match token {
130                        Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue,
131                        Ok(gix_object::commit::ref_iter::Token::Parent { id }) => return Some(Ok(id)),
132                        Ok(_unused_token) => break,
133                        Err(err) => return Some(Err(err.into())),
134                    }
135                }
136                None
137            }
138            Either::Right((cache, it)) => it
139                .next()
140                .map(|r| r.map(|pos| cache.id_at(pos).to_owned()).map_err(Into::into)),
141        }
142    }
143}
144
145///
146pub mod iter_parents {
147    /// The error returned by the [`Parents`][super::Parents] iterator.
148    #[derive(Debug, thiserror::Error)]
149    #[allow(missing_docs)]
150    pub enum Error {
151        #[error("An error occurred when parsing commit parents")]
152        DecodeCommit(#[from] gix_object::decode::Error),
153        #[error("An error occurred when parsing parents from the commit graph")]
154        DecodeCommitGraph(#[from] gix_commitgraph::file::commit::Error),
155    }
156}
157
158///
159pub mod to_owned {
160    /// The error returned by [`to_owned()`][crate::graph::LazyCommit::to_owned()].
161    #[derive(Debug, thiserror::Error)]
162    #[allow(missing_docs)]
163    pub enum Error {
164        #[error("A commit could not be decoded during traversal")]
165        Decode(#[from] gix_object::decode::Error),
166        #[error("Could not find commit position in graph when traversing parents")]
167        CommitGraphParent(#[from] gix_commitgraph::file::commit::Error),
168        #[error("Commit-graph time could not be presented as signed integer: {actual}")]
169        CommitGraphTime { actual: u64 },
170    }
171}