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