Get rid of `file::verify::EitherError` in favor of having `graph::verify::Error` manually copy file errors into the graph error object.
Showing the changes to git-commitgraph (3 of 3 files). Show all
Unified · Split
| @@ -10,7 +10,7 @@ use std::convert::TryFrom; · 9 unmodified lines | |||
| 10 | use std::path::Path; | 10 | use std::path::Path; |
| 11 | 11 | ||
| 12 | #[derive(thiserror::Error, Debug)] | 12 | #[derive(thiserror::Error, Debug)] |
| 13 | pub enum Error { | 13 | pub enum Error<E: std::error::Error + 'static> { |
| 14 | #[error(transparent)] | 14 | #[error(transparent)] |
| 15 | Commit(#[from] file::commit::Error), | 15 | Commit(#[from] file::commit::Error), |
| 16 | #[error("commit at file position {pos} has invalid ID {id}")] | 16 | #[error("commit at file position {pos} has invalid ID {id}")] |
| @@ -26,24 +26,13 @@ pub enum Error { · 9 unmodified lines | |||
| 26 | #[error("commit {id} has invalid generation {generation}")] | 26 | #[error("commit {id} has invalid generation {generation}")] |
| 27 | Generation { generation: u32, id: owned::Id }, | 27 | Generation { generation: u32, id: owned::Id }, |
| 28 | #[error("checksum mismatch: expected {expected}, got {actual}")] | 28 | #[error("checksum mismatch: expected {expected}, got {actual}")] |
| 29 | Mismatch { expected: owned::Id, actual: owned::Id }, | 29 | Mismatch { actual: owned::Id, expected: owned::Id }, |
| 30 | #[error("{0}")] | ||
| 31 | Processor(#[source] E), | ||
| 30 | #[error("commit {id} has invalid root tree ID {root_tree_id}")] | 32 | #[error("commit {id} has invalid root tree ID {root_tree_id}")] |
| 31 | RootTreeId { id: owned::Id, root_tree_id: owned::Id }, | 33 | RootTreeId { id: owned::Id, root_tree_id: owned::Id }, |
| 32 | } | 34 | } |
| 33 | 35 | ||
| 34 | // This is a separate type to let `traverse`'s caller use the same error type for its result and its | ||
| 35 | // processor error type while also letting that error type contain file::verify::Error values. | ||
| 36 | // Is there a better way? Should the caller's error type just use boxes to avoid recursive type | ||
| 37 | // errors? | ||
| 38 | #[derive(thiserror::Error, Debug)] | ||
| 39 | pub enum EitherError<E1: std::error::Error + 'static, E2: std::error::Error + 'static> { | ||
| 40 | #[error(transparent)] | ||
| 41 | Internal(#[from] E1), | ||
| 42 | // Why can't I use #[from] here? Boo! | ||
| 43 | #[error("{0}")] | ||
| 44 | Processor(#[source] E2), | ||
| 45 | } | ||
| 46 | |||
| 47 | #[derive(Clone, Debug, Eq, PartialEq)] | 36 | #[derive(Clone, Debug, Eq, PartialEq)] |
| 48 | #[cfg_attr(feature = "serde1", derive(serde::Deserialize, serde::Serialize))] | 37 | #[cfg_attr(feature = "serde1", derive(serde::Deserialize, serde::Serialize))] |
| 49 | pub struct Outcome { | 38 | pub struct Outcome { |
| @@ -59,13 +48,14 @@ impl File { · 9 unmodified lines | |||
| 59 | borrowed::Id::try_from(&self.data[self.data.len() - SHA1_SIZE..]).expect("file to be large enough for a hash") | 48 | borrowed::Id::try_from(&self.data[self.data.len() - SHA1_SIZE..]).expect("file to be large enough for a hash") |
| 60 | } | 49 | } |
| 61 | 50 | ||
| 62 | pub fn traverse<'a, E, Processor>(&'a self, mut processor: Processor) -> Result<Outcome, EitherError<Error, E>> | 51 | pub fn traverse<'a, E, Processor>(&'a self, mut processor: Processor) -> Result<Outcome, Error<E>> |
| 63 | where | 52 | where |
| 64 | E: std::error::Error + 'static, | 53 | E: std::error::Error + 'static, |
| 65 | Processor: FnMut(&file::Commit<'a>) -> Result<(), E>, | 54 | Processor: FnMut(&file::Commit<'a>) -> Result<(), E>, |
| 66 | { | 55 | { |
| 67 | self.verify_checksum()?; | 56 | self.verify_checksum() |
| 68 | verify_split_chain_filename_hash(&self.path, self.checksum())?; | 57 | .map_err(|(actual, expected)| Error::Mismatch { actual, expected })?; |
| 58 | verify_split_chain_filename_hash(&self.path, self.checksum()).map_err(Error::Filename)?; | ||
| 69 | 59 | ||
| 70 | // This probably belongs in borrowed::Id itself? | 60 | // This probably belongs in borrowed::Id itself? |
| 71 | let null_id = borrowed::Id::from(&[0u8; SHA1_SIZE]); | 61 | let null_id = borrowed::Id::from(&[0u8; SHA1_SIZE]); |
| @@ -86,32 +76,28 @@ impl File { · 14 unmodified lines | |||
| 86 | return Err(Error::CommitId { | 76 | return Err(Error::CommitId { |
| 87 | pos: commit.position(), | 77 | pos: commit.position(), |
| 88 | id: commit.id().into(), | 78 | id: commit.id().into(), |
| 89 | } | 79 | }); |
| 90 | .into()); | ||
| 91 | } | 80 | } |
| 92 | return Err(Error::CommitsOutOfOrder { | 81 | return Err(Error::CommitsOutOfOrder { |
| 93 | pos: commit.position(), | 82 | pos: commit.position(), |
| 94 | id: commit.id().into(), | 83 | id: commit.id().into(), |
| 95 | predecessor_id: prev_id.into(), | 84 | predecessor_id: prev_id.into(), |
| 96 | } | 85 | }); |
| 97 | .into()); | ||
| 98 | } | 86 | } |
| 99 | if commit.root_tree_id() == null_id { | 87 | if commit.root_tree_id() == null_id { |
| 100 | return Err(Error::RootTreeId { | 88 | return Err(Error::RootTreeId { |
| 101 | id: commit.id().into(), | 89 | id: commit.id().into(), |
| 102 | root_tree_id: commit.root_tree_id().into(), | 90 | root_tree_id: commit.root_tree_id().into(), |
| 103 | } | 91 | }); |
| 104 | .into()); | ||
| 105 | } | 92 | } |
| 106 | if commit.generation() > GENERATION_NUMBER_MAX { | 93 | if commit.generation() > GENERATION_NUMBER_MAX { |
| 107 | return Err(Error::Generation { | 94 | return Err(Error::Generation { |
| 108 | generation: commit.generation(), | 95 | generation: commit.generation(), |
| 109 | id: commit.id().into(), | 96 | id: commit.id().into(), |
| 110 | } | 97 | }); |
| 111 | .into()); | ||
| 112 | } | 98 | } |
| 113 | 99 | ||
| 114 | processor(&commit).map_err(EitherError::Processor)?; | 100 | processor(&commit).map_err(Error::Processor)?; |
| 115 | 101 | ||
| 116 | stats.max_generation = max(stats.max_generation, commit.generation()); | 102 | stats.max_generation = max(stats.max_generation, commit.generation()); |
| 117 | stats.min_generation = min(stats.min_generation, commit.generation()); | 103 | stats.min_generation = min(stats.min_generation, commit.generation()); |
| @@ -130,7 +116,7 @@ impl File { · 12 unmodified lines | |||
| 130 | Ok(stats) | 116 | Ok(stats) |
| 131 | } | 117 | } |
| 132 | 118 | ||
| 133 | pub fn verify_checksum(&self) -> Result<owned::Id, Error> { | 119 | pub fn verify_checksum(&self) -> Result<owned::Id, (owned::Id, owned::Id)> { |
| 134 | // TODO: Use/copy git_odb::hash::bytes_of_file. | 120 | // TODO: Use/copy git_odb::hash::bytes_of_file. |
| 135 | let data_len_without_trailer = self.data.len() - SHA1_SIZE; | 121 | let data_len_without_trailer = self.data.len() - SHA1_SIZE; |
| 136 | let mut hasher = git_features::hash::Sha1::default(); | 122 | let mut hasher = git_features::hash::Sha1::default(); |
| @@ -141,17 +127,14 @@ impl File { · 4 unmodified lines | |||
| 141 | if actual.to_borrowed() == expected { | 127 | if actual.to_borrowed() == expected { |
| 142 | Ok(actual) | 128 | Ok(actual) |
| 143 | } else { | 129 | } else { |
| 144 | Err(Error::Mismatch { | 130 | Err((actual, expected.into())) |
| 145 | actual, | ||
| 146 | expected: expected.into(), | ||
| 147 | }) | ||
| 148 | } | 131 | } |
| 149 | } | 132 | } |
| 150 | } | 133 | } |
| 151 | 134 | ||
| 152 | /// If the given path's filename matches "graph-{hash}.graph", check that `hash` matches the | 135 | /// If the given path's filename matches "graph-{hash}.graph", check that `hash` matches the |
| 153 | /// expected hash. | 136 | /// expected hash. |
| 154 | fn verify_split_chain_filename_hash(path: impl AsRef<Path>, expected: borrowed::Id<'_>) -> Result<(), Error> { | 137 | fn verify_split_chain_filename_hash(path: impl AsRef<Path>, expected: borrowed::Id<'_>) -> Result<(), String> { |
| 155 | let path = path.as_ref(); | 138 | let path = path.as_ref(); |
| 156 | path.file_name() | 139 | path.file_name() |
| 157 | .and_then(|filename| filename.to_str()) | 140 | .and_then(|filename| filename.to_str()) |
| @@ -159,9 +142,6 @@ fn verify_split_chain_filename_hash(path: impl AsRef<Path>, expected: borrowed:: · 1 unmodified line | |||
| 159 | .and_then(|stem| stem.strip_prefix("graph-")) | 142 | .and_then(|stem| stem.strip_prefix("graph-")) |
| 160 | .map_or(Ok(()), |hex| match owned::Id::from_40_bytes_in_hex(hex.as_bytes()) { | 143 | .map_or(Ok(()), |hex| match owned::Id::from_40_bytes_in_hex(hex.as_bytes()) { |
| 161 | Ok(actual) if actual.to_borrowed() == expected => Ok(()), | 144 | Ok(actual) if actual.to_borrowed() == expected => Ok(()), |
| 162 | _ => Err(Error::Filename(format!( | 145 | _ => Err(format!("graph-{}.graph", expected.to_sha1_hex().as_bstr())), |
| 163 | "graph-{}.graph", | ||
| 164 | expected.to_sha1_hex().as_bstr() | ||
| 165 | ))), | ||
| 166 | }) | 146 | }) |
| 167 | } | 147 | } |
| @@ -1,6 +1,6 @@ | |||
| 1 | use crate::{ | 1 | use crate::{ |
| 2 | file::{self, commit}, | 2 | file::{self, commit}, |
| 3 | graph, Graph, GENERATION_NUMBER_MAX, | 3 | graph, Graph, ImpossibleVariantError, GENERATION_NUMBER_MAX, |
| 4 | }; | 4 | }; |
| 5 | use git_object::owned; | 5 | use git_object::owned; |
| 6 | use std::cmp::{max, min}; | 6 | use std::cmp::{max, min}; |
| @@ -22,7 +22,15 @@ pub enum Error<E: std::error::Error + 'static> { · 15 unmodified lines | |||
| 22 | #[error(transparent)] | 22 | #[error(transparent)] |
| 23 | Commit(#[from] commit::Error), | 23 | Commit(#[from] commit::Error), |
| 24 | #[error("{}: {err}", .path.display())] | 24 | #[error("{}: {err}", .path.display())] |
| 25 | File { err: file::verify::Error, path: PathBuf }, | 25 | File { |
| 26 | // Use zero-size error type. We will never return | ||
| 27 | // `graph::verify::Error::File(file::verify::Error::Processor(...))`, because we are the | ||
| 28 | // file's processor, and we convert`file::verify::Error::Processor<graph::verify::Error>` | ||
| 29 | // variants into direct `graph::verify::Error` values. | ||
| 30 | // TODO: Use never type when it becomes available. | ||
| 31 | err: file::verify::Error<ImpossibleVariantError>, | ||
| 32 | path: PathBuf, | ||
| 33 | }, | ||
| 26 | #[error("Commit {id}'s generation should be {expected} but is {actual}")] | 34 | #[error("Commit {id}'s generation should be {expected} but is {actual}")] |
| 27 | Generation { actual: u32, expected: u32, id: owned::Id }, | 35 | Generation { actual: u32, expected: u32, id: owned::Id }, |
| 28 | #[error( | 36 | #[error( |
| @@ -132,12 +140,32 @@ impl Graph { · 103 unmodified lines | |||
| 132 | 140 | ||
| 133 | Ok(()) | 141 | Ok(()) |
| 134 | }) | 142 | }) |
| 135 | .map_err(|e| match e { | 143 | .map_err(|err| Error::File { |
| 136 | file::verify::EitherError::Internal(err) => Error::File { | 144 | err: match err { |
| 137 | err, | 145 | file::verify::Error::Processor(e) => return e, |
| 138 | path: file.path().to_owned(), | 146 | file::verify::Error::RootTreeId { id, root_tree_id } => { |
| 147 | file::verify::Error::RootTreeId { id, root_tree_id } | ||
| 148 | } | ||
| 149 | file::verify::Error::Mismatch { actual, expected } => { | ||
| 150 | file::verify::Error::Mismatch { actual, expected } | ||
| 151 | } | ||
| 152 | file::verify::Error::Generation { generation, id } => { | ||
| 153 | file::verify::Error::Generation { generation, id } | ||
| 154 | } | ||
| 155 | file::verify::Error::Filename(expected) => file::verify::Error::Filename(expected), | ||
| 156 | file::verify::Error::Commit(err) => file::verify::Error::Commit(err), | ||
| 157 | file::verify::Error::CommitId { id, pos } => file::verify::Error::CommitId { id, pos }, | ||
| 158 | file::verify::Error::CommitsOutOfOrder { | ||
| 159 | id, | ||
| 160 | pos, | ||
| 161 | predecessor_id, | ||
| 162 | } => file::verify::Error::CommitsOutOfOrder { | ||
| 163 | id, | ||
| 164 | pos, | ||
| 165 | predecessor_id, | ||
| 166 | }, | ||
| 139 | }, | 167 | }, |
| 140 | file::verify::EitherError::Processor(e) => e, | 168 | path: file.path().to_owned(), |
| 141 | })?; | 169 | })?; |
| 142 | 170 | ||
| 143 | max_generation = max(max_generation, file_stats.max_generation); | 171 | max_generation = max(max_generation, file_stats.max_generation); |
| @@ -11,3 +11,15 @@ pub const GENERATION_NUMBER_MAX: u32 = 0x3fff_ffff; · 10 unmodified lines | |||
| 11 | 11 | ||
| 12 | /// The maximum number of commits that can be stored in a commit graph. | 12 | /// The maximum number of commits that can be stored in a commit graph. |
| 13 | pub const MAX_COMMITS: u32 = (1 << 30) + (1 << 29) + (1 << 28) - 1; | 13 | pub const MAX_COMMITS: u32 = (1 << 30) + (1 << 29) + (1 << 28) - 1; |
| 14 | |||
| 15 | // TODO: pub type ImpossibleVariantError = !; | ||
| 16 | #[derive(Debug)] | ||
| 17 | pub struct ImpossibleVariantError; | ||
| 18 | |||
| 19 | impl std::error::Error for ImpossibleVariantError {} | ||
| 20 | |||
| 21 | impl std::fmt::Display for ImpossibleVariantError { | ||
| 22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 23 | f.write_str("this enum was constructed with an invalid variant") | ||
| 24 | } | ||
| 25 | } | ||