diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:57:31 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:57:31 +0000 |
commit | dc0db358abe19481e475e10c32149b53370f1a1c (patch) | |
tree | ab8ce99c4b255ce46f99ef402c27916055b899ee /vendor/gix-commitgraph/src/file/commit.rs | |
parent | Releasing progress-linux version 1.71.1+dfsg1-2~progress7.99u1. (diff) | |
download | rustc-dc0db358abe19481e475e10c32149b53370f1a1c.tar.xz rustc-dc0db358abe19481e475e10c32149b53370f1a1c.zip |
Merging upstream version 1.72.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-commitgraph/src/file/commit.rs')
-rw-r--r-- | vendor/gix-commitgraph/src/file/commit.rs | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/vendor/gix-commitgraph/src/file/commit.rs b/vendor/gix-commitgraph/src/file/commit.rs new file mode 100644 index 000000000..14fe15e84 --- /dev/null +++ b/vendor/gix-commitgraph/src/file/commit.rs @@ -0,0 +1,258 @@ +//! Low-level operations on individual commits. +use std::{ + convert::TryInto, + fmt::{Debug, Formatter}, + slice::Chunks, +}; + +use crate::{ + file::{self, EXTENDED_EDGES_MASK, LAST_EXTENDED_EDGE_MASK, NO_PARENT}, + File, Position, +}; + +/// The error used in the [`file::commit`][self] module. +#[derive(thiserror::Error, Debug)] +#[allow(missing_docs)] +pub enum Error { + #[error("commit {0}'s extra edges overflows the commit-graph file's extra edges list")] + ExtraEdgesListOverflow(gix_hash::ObjectId), + #[error("commit {0}'s first parent is an extra edge index, which is invalid")] + FirstParentIsExtraEdgeIndex(gix_hash::ObjectId), + #[error("commit {0} has extra edges, but commit-graph file has no extra edges list")] + MissingExtraEdgesList(gix_hash::ObjectId), + #[error("commit {0} has a second parent but not a first parent")] + SecondParentWithoutFirstParent(gix_hash::ObjectId), +} + +/// A commit as stored in a [`File`]. +#[derive(Copy, Clone)] +pub struct Commit<'a> { + file: &'a File, + pos: file::Position, + // We can parse the below fields lazily if needed. + commit_timestamp: u64, + generation: u32, + parent1: ParentEdge, + parent2: ParentEdge, + root_tree_id: &'a gix_hash::oid, +} + +#[inline] +fn read_u32(b: &[u8]) -> u32 { + u32::from_be_bytes(b.try_into().unwrap()) +} + +impl<'a> Commit<'a> { + pub(crate) fn new(file: &'a File, pos: file::Position) -> Self { + let bytes = file.commit_data_bytes(pos); + Commit { + file, + pos, + root_tree_id: gix_hash::oid::from_bytes_unchecked(&bytes[..file.hash_len]), + parent1: ParentEdge::from_raw(read_u32(&bytes[file.hash_len..][..4])), + parent2: ParentEdge::from_raw(read_u32(&bytes[file.hash_len + 4..][..4])), + // TODO: Add support for corrected commit date offset overflow. + // See https://github.com/git/git/commit/e8b63005c48696a26f976f5f9b0ccaf1983e439d and + // https://github.com/git/git/commit/f90fca638e99a031dce8e3aca72427b2f9b4bb38 for more details and hints at a test. + generation: read_u32(&bytes[file.hash_len + 8..][..4]) >> 2, + commit_timestamp: u64::from_be_bytes(bytes[file.hash_len + 8..][..8].try_into().unwrap()) + & 0x0003_ffff_ffff, + } + } + + /// Returns the committer timestamp of this commit. + /// + /// The value is the number of seconds since 1970-01-01 00:00:00 UTC. + pub fn committer_timestamp(&self) -> u64 { + self.commit_timestamp + } + + /// Returns the generation number of this commit. + /// + /// Commits without parents have generation number 1. Commits with parents have a generation + /// number that is the max of their parents' generation numbers + 1. + pub fn generation(&self) -> u32 { + self.generation + } + + /// Returns an iterator over the parent positions for lookup in the owning [Graph][crate::Graph]. + pub fn iter_parents(self) -> Parents<'a> { + // I didn't find a combinator approach that a) was as strict as ParentIterator, b) supported + // fuse-after-first-error behavior, and b) was significantly shorter or more understandable + // than ParentIterator. So here we are. + Parents { + commit_data: self, + state: ParentIteratorState::First, + } + } + + /// Returns the hash of this commit. + pub fn id(&self) -> &'a gix_hash::oid { + self.file.id_at(self.pos) + } + + /// Returns the first parent of this commit. + pub fn parent1(&self) -> Result<Option<Position>, Error> { + self.iter_parents().next().transpose() + } + + /// Returns the position at which this commit is stored in the parent [File]. + pub fn position(&self) -> file::Position { + self.pos + } + + /// Return the hash of the tree this commit points to. + pub fn root_tree_id(&self) -> &gix_hash::oid { + self.root_tree_id + } +} + +impl<'a> Debug for Commit<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Commit {{ id: {}, lex_pos: {}, generation: {}, root_tree_id: {}, parent1: {:?}, parent2: {:?} }}", + self.id(), + self.pos, + self.generation(), + self.root_tree_id(), + self.parent1, + self.parent2, + ) + } +} + +impl<'a> Eq for Commit<'a> {} + +impl<'a> PartialEq for Commit<'a> { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self.file, other.file) && self.pos == other.pos + } +} + +/// An iterator over parents of a [`Commit`]. +pub struct Parents<'a> { + commit_data: Commit<'a>, + state: ParentIteratorState<'a>, +} + +impl<'a> Iterator for Parents<'a> { + type Item = Result<Position, Error>; + + fn next(&mut self) -> Option<Self::Item> { + let state = std::mem::replace(&mut self.state, ParentIteratorState::Exhausted); + match state { + ParentIteratorState::First => match self.commit_data.parent1 { + ParentEdge::None => match self.commit_data.parent2 { + ParentEdge::None => None, + _ => Some(Err(Error::SecondParentWithoutFirstParent(self.commit_data.id().into()))), + }, + ParentEdge::GraphPosition(pos) => { + self.state = ParentIteratorState::Second; + Some(Ok(pos)) + } + ParentEdge::ExtraEdgeIndex(_) => { + Some(Err(Error::FirstParentIsExtraEdgeIndex(self.commit_data.id().into()))) + } + }, + ParentIteratorState::Second => match self.commit_data.parent2 { + ParentEdge::None => None, + ParentEdge::GraphPosition(pos) => Some(Ok(pos)), + ParentEdge::ExtraEdgeIndex(extra_edge_index) => { + if let Some(extra_edges_list) = self.commit_data.file.extra_edges_data() { + let start_offset: usize = extra_edge_index + .try_into() + .expect("an architecture able to hold 32 bits of integer"); + let start_offset = start_offset + .checked_mul(4) + .expect("an extended edge index small enough to fit in usize"); + if let Some(tail) = extra_edges_list.get(start_offset..) { + self.state = ParentIteratorState::Extra(tail.chunks(4)); + // This recursive call is what blocks me from replacing ParentIterator + // with a std::iter::from_fn closure. + self.next() + } else { + Some(Err(Error::ExtraEdgesListOverflow(self.commit_data.id().into()))) + } + } else { + Some(Err(Error::MissingExtraEdgesList(self.commit_data.id().into()))) + } + } + }, + ParentIteratorState::Extra(mut chunks) => { + if let Some(chunk) = chunks.next() { + let extra_edge = read_u32(chunk); + match ExtraEdge::from_raw(extra_edge) { + ExtraEdge::Internal(pos) => { + self.state = ParentIteratorState::Extra(chunks); + Some(Ok(pos)) + } + ExtraEdge::Last(pos) => Some(Ok(pos)), + } + } else { + Some(Err(Error::ExtraEdgesListOverflow(self.commit_data.id().into()))) + } + } + ParentIteratorState::Exhausted => None, + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + match (&self.state, self.commit_data.parent1, self.commit_data.parent2) { + (ParentIteratorState::First, ParentEdge::None, ParentEdge::None) => (0, Some(0)), + (ParentIteratorState::First, ParentEdge::None, _) => (1, Some(1)), + (ParentIteratorState::First, ParentEdge::GraphPosition(_), ParentEdge::None) => (1, Some(1)), + (ParentIteratorState::First, ParentEdge::GraphPosition(_), ParentEdge::GraphPosition(_)) => (2, Some(2)), + (ParentIteratorState::First, ParentEdge::GraphPosition(_), ParentEdge::ExtraEdgeIndex(_)) => (3, None), + (ParentIteratorState::First, ParentEdge::ExtraEdgeIndex(_), _) => (1, Some(1)), + (ParentIteratorState::Second, _, ParentEdge::None) => (0, Some(0)), + (ParentIteratorState::Second, _, ParentEdge::GraphPosition(_)) => (1, Some(1)), + (ParentIteratorState::Second, _, ParentEdge::ExtraEdgeIndex(_)) => (2, None), + (ParentIteratorState::Extra(_), _, _) => (1, None), + (ParentIteratorState::Exhausted, _, _) => (0, Some(0)), + } + } +} + +#[derive(Debug)] +enum ParentIteratorState<'a> { + First, + Second, + Extra(Chunks<'a, u8>), + Exhausted, +} + +#[derive(Clone, Copy, Debug)] +enum ParentEdge { + None, + GraphPosition(Position), + ExtraEdgeIndex(u32), +} + +impl ParentEdge { + pub fn from_raw(raw: u32) -> ParentEdge { + if raw == NO_PARENT { + return ParentEdge::None; + } + if raw & EXTENDED_EDGES_MASK != 0 { + ParentEdge::ExtraEdgeIndex(raw & !EXTENDED_EDGES_MASK) + } else { + ParentEdge::GraphPosition(Position(raw)) + } + } +} + +enum ExtraEdge { + Internal(Position), + Last(Position), +} + +impl ExtraEdge { + pub fn from_raw(raw: u32) -> Self { + if raw & LAST_EXTENDED_EDGE_MASK != 0 { + Self::Last(Position(raw & !LAST_EXTENDED_EDGE_MASK)) + } else { + Self::Internal(Position(raw)) + } + } +} |