Showing the changes to git-commitgraph/src (5 of 6 files). Show all
Unified · Split
| @@ -9,6 +9,7 @@ use std::{ · 8 unmodified lines | ||
| 9 | 9 | |
| 10 | 10 | /// Access |
| 11 | 11 | impl File { |
| 12 | + /// The amount of base graphs that this file depends on. | |
| 12 | 13 | pub fn base_graph_count(&self) -> u8 { |
| 13 | 14 | self.base_graph_count |
| 14 | 15 | } |
| @@ -24,6 +25,9 @@ impl File { · 9 unmodified lines | ||
| 24 | 25 | Commit::new(self, pos) |
| 25 | 26 | } |
| 26 | 27 | |
| 28 | + /// The kind of hash used in this File. | |
| 29 | + /// | |
| 30 | + /// Note that it is always conforming to the hash used in the owning repository. | |
| 27 | 31 | pub fn hash_kind(&self) -> HashKind { |
| 28 | 32 | HashKind::Sha1 |
| 29 | 33 | } |
| @@ -46,6 +50,7 @@ impl File { · 16 unmodified lines | ||
| 46 | 50 | borrowed::Id::try_from(&self.data[start..start + SHA1_SIZE]).expect("20 bytes SHA1 to be alright") |
| 47 | 51 | } |
| 48 | 52 | |
| 53 | + /// Return an iterator over all object hashes stored in the base graph. | |
| 49 | 54 | pub fn iter_base_graph_ids(&self) -> impl Iterator<Item = borrowed::Id<'_>> { |
| 50 | 55 | let start = self.base_graphs_list_offset.unwrap_or(0); |
| 51 | 56 | let base_graphs_list = &self.data[start..start + (SHA1_SIZE * usize::from(self.base_graph_count))]; |
| @@ -54,14 +59,17 @@ impl File { · 2 unmodified lines | ||
| 54 | 59 | .map(|bytes| borrowed::Id::try_from(bytes).expect("20 bytes SHA1 to be alright")) |
| 55 | 60 | } |
| 56 | 61 | |
| 62 | + /// return an iterator over all commits in this file. | |
| 57 | 63 | pub fn iter_commits(&self) -> impl Iterator<Item = Commit<'_>> { |
| 58 | 64 | (0..self.num_commits()).map(move |i| self.commit_at(file::Position(i))) |
| 59 | 65 | } |
| 60 | 66 | |
| 67 | + /// Return an iterator over all object hashes stored in this file. | |
| 61 | 68 | pub fn iter_ids(&self) -> impl Iterator<Item = borrowed::Id<'_>> { |
| 62 | 69 | (0..self.num_commits()).map(move |i| self.id_at(file::Position(i))) |
| 63 | 70 | } |
| 64 | 71 | |
| 72 | + /// Translate the given object hash to its position within this file, if present. | |
| 65 | 73 | // copied from git-odb/src/pack/index/access.rs |
| 66 | 74 | pub fn lookup(&self, id: borrowed::Id<'_>) -> Option<file::Position> { |
| 67 | 75 | let first_byte = usize::from(id.first_byte()); |
| @@ -94,6 +102,7 @@ impl File { · 26 unmodified lines | ||
| 94 | 102 | self.fan[255] |
| 95 | 103 | } |
| 96 | 104 | |
| 105 | + /// Returns the path to this file. | |
| 97 | 106 | pub fn path(&self) -> &Path { |
| 98 | 107 | &self.path |
| 99 | 108 | } |
| @@ -11,7 +11,9 @@ use std::{ · 10 unmodified lines | ||
| 11 | 11 | slice::Chunks, |
| 12 | 12 | }; |
| 13 | 13 | |
| 14 | +/// The error used in the [`file::commit`][self] module. | |
| 14 | 15 | #[derive(thiserror::Error, Debug)] |
| 16 | +#[allow(missing_docs)] | |
| 15 | 17 | pub enum Error { |
| 16 | 18 | #[error("commit {0}'s extra edges overflows the commit-graph file's extra edges list")] |
| 17 | 19 | ExtraEdgesListOverflow(owned::Id), |
| @@ -28,6 +30,7 @@ pub enum Error { · 10 unmodified lines | ||
| 28 | 30 | const NO_PARENT: u32 = 0x7000_0000; |
| 29 | 31 | const EXTENDED_EDGES_MASK: u32 = 0x8000_0000; |
| 30 | 32 | |
| 33 | +/// A commit as stored in a [`File`]. | |
| 31 | 34 | pub struct Commit<'a> { |
| 32 | 35 | file: &'a File, |
| 33 | 36 | pos: file::Position, |
| @@ -68,6 +71,7 @@ impl<'a> Commit<'a> { · 34 unmodified lines | ||
| 68 | 71 | self.generation |
| 69 | 72 | } |
| 70 | 73 | |
| 74 | + /// Returns an iterator over the parent positions for lookup in the owning [Graph][crate::Graph]. | |
| 71 | 75 | pub fn iter_parents(&'a self) -> impl Iterator<Item = Result<graph::Position, Error>> + 'a { |
| 72 | 76 | // I didn't find a combinator approach that a) was as strict as ParentIterator, b) supported |
| 73 | 77 | // fuse-after-first-error behavior, and b) was significantly shorter or more understandable |
| @@ -78,29 +82,23 @@ impl<'a> Commit<'a> { · 4 unmodified lines | ||
| 78 | 82 | } |
| 79 | 83 | } |
| 80 | 84 | |
| 81 | − // Allow the return value to outlive this Commit object, as it only needs to be bound by the | |
| 82 | − // lifetime of the parent file. | |
| 83 | − pub fn id<'b>(&'b self) -> borrowed::Id<'a> | |
| 84 | − where | |
| 85 | − 'a: 'b, | |
| 86 | − { | |
| 85 | + /// Returns the hash of this commit. | |
| 86 | + pub fn id(&self) -> borrowed::Id<'a> { | |
| 87 | 87 | self.file.id_at(self.pos) |
| 88 | 88 | } |
| 89 | 89 | |
| 90 | + /// Returns the first parent of this commit. | |
| 90 | 91 | pub fn parent1(&self) -> Result<Option<graph::Position>, Error> { |
| 91 | 92 | self.iter_parents().next().transpose() |
| 92 | 93 | } |
| 93 | 94 | |
| 95 | + /// Returns the position at which this commit is stored in the parent [File]. | |
| 94 | 96 | pub fn position(&self) -> file::Position { |
| 95 | 97 | self.pos |
| 96 | 98 | } |
| 97 | 99 | |
| 98 | − // Allow the return value to outlive this Commit object, as it only needs to be bound by the | |
| 99 | − // lifetime of the parent file. | |
| 100 | − pub fn root_tree_id<'b>(&'b self) -> borrowed::Id<'a> | |
| 101 | − where | |
| 102 | − 'a: 'b, | |
| 103 | − { | |
| 100 | + /// Return the hash of the tree this commit points to. | |
| 101 | + pub fn root_tree_id(&self) -> borrowed::Id<'a> { | |
| 104 | 102 | self.root_tree_id |
| 105 | 103 | } |
| 106 | 104 | } |
| @@ -128,6 +126,7 @@ impl<'a> PartialEq for Commit<'a> { · 21 unmodified lines | ||
| 128 | 126 | } |
| 129 | 127 | } |
| 130 | 128 | |
| 129 | +/// An iterator over parents of a [`Commit`]. | |
| 131 | 130 | pub struct ParentIterator<'a> { |
| 132 | 131 | commit_data: &'a Commit<'a>, |
| 133 | 132 | state: ParentIteratorState<'a>, |
| @@ -11,7 +11,9 @@ use std::{ · 10 unmodified lines | ||
| 11 | 11 | |
| 12 | 12 | type ChunkId = [u8; 4]; |
| 13 | 13 | |
| 14 | +/// The error used in [`File::at()`]. | |
| 14 | 15 | #[derive(thiserror::Error, Debug)] |
| 16 | +#[allow(missing_docs)] | |
| 15 | 17 | pub enum Error { |
| 16 | 18 | #[error("Commit-graph {:?} chunk contains {from_chunk} base graphs, but commit-graph file header claims {from_header} base graphs", BASE_GRAPHS_LIST_CHUNK_ID.as_bstr())] |
| 17 | 19 | BaseGraphMismatch { from_header: u8, from_chunk: u32 }, |
| @@ -63,6 +65,7 @@ const OID_LOOKUP_CHUNK_ID: ChunkId = *b"OIDL"; · 45 unmodified lines | ||
| 63 | 65 | const SENTINEL_CHUNK_ID: ChunkId = [0u8; 4]; |
| 64 | 66 | |
| 65 | 67 | impl File { |
| 68 | + /// Try to parse the commit graph file at `path`. | |
| 66 | 69 | pub fn at(path: impl AsRef<Path>) -> Result<File, Error> { |
| 67 | 70 | Self::try_from(path.as_ref()) |
| 68 | 71 | } |
| @@ -1,12 +1,17 @@ | ||
| 1 | 1 | //! Operations on a single commit-graph file. |
| 2 | + | |
| 2 | 3 | mod access; |
| 4 | + | |
| 5 | +/// | |
| 3 | 6 | pub mod commit; |
| 7 | +pub use commit::Commit; | |
| 8 | + | |
| 4 | 9 | mod init; |
| 10 | +/// | |
| 5 | 11 | pub mod verify; |
| 6 | 12 | |
| 7 | 13 | pub use init::Error; |
| 8 | 14 | |
| 9 | −pub use commit::Commit; | |
| 10 | 15 | use filebuffer::FileBuffer; |
| 11 | 16 | use git_hash::SIZE_OF_SHA1_DIGEST as SHA1_SIZE; |
| 12 | 17 | use std::{ |
| @@ -22,7 +27,7 @@ const SIGNATURE: &[u8] = b"CGPH"; · 9 unmodified lines | ||
| 22 | 27 | /// A single commit-graph file. |
| 23 | 28 | /// |
| 24 | 29 | /// All operations on a `File` are local to that graph file. Since a commit graph can span multiple |
| 25 | −/// files, all interesting graph operations belong on `Graph`. | |
| 30 | +/// files, all interesting graph operations belong on [`Graph`][crate::Graph]. | |
| 26 | 31 | pub struct File { |
| 27 | 32 | base_graph_count: u8, |
| 28 | 33 | base_graphs_list_offset: Option<usize>, |
| @@ -39,8 +44,8 @@ pub struct File { · 10 unmodified lines | ||
| 39 | 44 | /// Commits within a graph file are sorted in lexicographical order by OID; a commit's lexigraphical position |
| 40 | 45 | /// is its position in this ordering. If a commit graph spans multiple files, each file's commits |
| 41 | 46 | /// start at lexigraphical position 0, so it is unique across a single file but is not unique across |
| 42 | −/// the whole commit graph. Each commit also has a graph position (`graph::Position`), which is unique | |
| 43 | −/// across the whole commit graph. In order to avoid accidentally mixing lexigraphical positions with graph | |
| 47 | +/// the whole commit graph. Each commit also has a graph position ([`graph::Position`][crate::graph::Position]), | |
| 48 | +/// which is unique /// across the whole commit graph. In order to avoid accidentally mixing lexigraphical positions with graph | |
| 44 | 49 | /// positions, distinct types are used for each. |
| 45 | 50 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] |
| 46 | 51 | pub struct Position(pub u32); |
| @@ -12,7 +12,9 @@ use std::{ · 11 unmodified lines | ||
| 12 | 12 | path::Path, |
| 13 | 13 | }; |
| 14 | 14 | |
| 15 | +/// The error used in [`File::traverse()`]. | |
| 15 | 16 | #[derive(thiserror::Error, Debug)] |
| 17 | +#[allow(missing_docs)] | |
| 16 | 18 | pub enum Error<E: std::error::Error + 'static> { |
| 17 | 19 | #[error(transparent)] |
| 18 | 20 | Commit(#[from] file::commit::Error), |
| @@ -36,21 +38,32 @@ pub enum Error<E: std::error::Error + 'static> { · 17 unmodified lines | ||
| 36 | 38 | RootTreeId { id: owned::Id, root_tree_id: owned::Id }, |
| 37 | 39 | } |
| 38 | 40 | |
| 41 | +/// The positive result of [`File::traverse()`] providing some statistical information. | |
| 39 | 42 | #[derive(Clone, Debug, Eq, PartialEq)] |
| 40 | 43 | #[cfg_attr(feature = "serde1", derive(serde::Deserialize, serde::Serialize))] |
| 41 | 44 | pub struct Outcome { |
| 45 | + /// The highest encountered [`file::Commit`] generation number. | |
| 42 | 46 | pub max_generation: u32, |
| 43 | − pub max_parents: u32, | |
| 47 | + /// The smallest encountered [`file::Commit`] generation number. | |
| 44 | 48 | pub min_generation: u32, |
| 49 | + /// The highest amount parents in a single [`file::Commit`]. | |
| 50 | + pub max_parents: u32, | |
| 51 | + /// The total amount of [`commits`][file::Commit] seen in the iteration. | |
| 45 | 52 | pub num_commits: u32, |
| 53 | + /// A mapping of `parent-count -> amount of [commits][file::Commit] with that count`. | |
| 46 | 54 | pub parent_counts: HashMap<u32, u32>, |
| 47 | 55 | } |
| 48 | 56 | |
| 57 | +/// Verification | |
| 49 | 58 | impl File { |
| 59 | + /// Returns the trailing checksum over the entire content of this file. | |
| 50 | 60 | pub fn checksum(&self) -> borrowed::Id<'_> { |
| 51 | 61 | borrowed::Id::try_from(&self.data[self.data.len() - SHA1_SIZE..]).expect("file to be large enough for a hash") |
| 52 | 62 | } |
| 53 | 63 | |
| 64 | + /// Traverse all [commits][file::Commit] stored in this file and call `processor(commit) -> Result<(), Error>` on it. | |
| 65 | + /// | |
| 66 | + /// If the `processor` fails, the iteration will be stopped and the entire call results in the respective error. | |
| 54 | 67 | pub fn traverse<'a, E, Processor>(&'a self, mut processor: Processor) -> Result<Outcome, Error<E>> |
| 55 | 68 | where |
| 56 | 69 | E: std::error::Error + 'static, |
| @@ -118,6 +131,10 @@ impl File { · 61 unmodified lines | ||
| 118 | 131 | Ok(stats) |
| 119 | 132 | } |
| 120 | 133 | |
| 134 | + /// Assure the [`checksum`][File::checksum()] matches the actual checksum over all content of this file, excluding the trailing | |
| 135 | + /// checksum itself. | |
| 136 | + /// | |
| 137 | + /// Return the actual checksum on success or `(actual checksum, expected checksum)` if there is a mismatch. | |
| 121 | 138 | pub fn verify_checksum(&self) -> Result<owned::Id, (owned::Id, owned::Id)> { |
| 122 | 139 | // Even though we could use git_features::hash::bytes_of_file(…), this would require using our own |
| 123 | 140 | // Error type to support io::Error and Mismatch. As we only gain progress, there probably isn't much value |