History

[commitgraph] Clean up `{file,graph}::verify::Error` types.

Get rid of `file::verify::EitherError` in favor of having `graph::verify::Error` manually copy file errors into the graph error object.

Conor Davis committed 6 years ago

Commit fa22cab1 parent 25711133 files · +64 −44Browse files

Showing the changes to git-commitgraph (3 of 3 files). Show all

Unified · Split

3 files
  1. git-commitgraph/src/file/verify.rs +17 −37
  2. git-commitgraph/src/graph/verify.rs +35 −7
  3. git-commitgraph/src/lib.rs +12 −0
git-commitgraph/src/file/verify.rs+17−37
@@ -10,7 +10,7 @@ use std::convert::TryFrom; · 9 unmodified lines
10use std::path::Path;10use std::path::Path;
11 11
12#[derive(thiserror::Error, Debug)]12#[derive(thiserror::Error, Debug)]
13pub enum Error {13pub 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)]
39pub 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))]
49pub struct Outcome {38pub 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 where52 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 the135/// If the given path's filename matches "graph-{hash}.graph", check that `hash` matches the
153/// expected hash.136/// expected hash.
154fn verify_split_chain_filename_hash(path: impl AsRef<Path>, expected: borrowed::Id<'_>) -> Result<(), Error> {137fn 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}
git-commitgraph/src/graph/verify.rs+35−7
@@ -1,6 +1,6 @@
1use crate::{1use crate::{
2 file::{self, commit},2 file::{self, commit},
3 graph, Graph, GENERATION_NUMBER_MAX,3 graph, Graph, ImpossibleVariantError, GENERATION_NUMBER_MAX,
4};4};
5use git_object::owned;5use git_object::owned;
6use std::cmp::{max, min};6use 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);
git-commitgraph/src/lib.rs+12−0
@@ -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.
13pub const MAX_COMMITS: u32 = (1 << 30) + (1 << 29) + (1 << 28) - 1;13pub const MAX_COMMITS: u32 = (1 << 30) + (1 << 29) + (1 << 28) - 1;
14
15// TODO: pub type ImpossibleVariantError = !;
16#[derive(Debug)]
17pub struct ImpossibleVariantError;
18
19impl std::error::Error for ImpossibleVariantError {}
20
21impl 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}