Unified · Split
| @@ -1,3 +1,4 @@ | |||
| 1 | use crate::pack; | ||
| 1 | use byteorder::{BigEndian, ByteOrder}; | 2 | use byteorder::{BigEndian, ByteOrder}; |
| 2 | use filebuffer::FileBuffer; | 3 | use filebuffer::FileBuffer; |
| 3 | use git_object::{self as object, SHA1_SIZE}; | 4 | use git_object::{self as object, SHA1_SIZE}; |
| @@ -54,6 +55,14 @@ quick_error! { · 50 unmodified lines | |||
| 54 | Mismatch { expected: object::Id, actual: object::Id } { | 55 | Mismatch { expected: object::Id, actual: object::Id } { |
| 55 | display("index checksum mismatch: expected {}, got {}", expected, actual) | 56 | display("index checksum mismatch: expected {}, got {}", expected, actual) |
| 56 | } | 57 | } |
| 58 | PackChecksum (err: pack::ChecksumError) { | ||
| 59 | display("The pack of this index file failed to verify its checksums") | ||
| 60 | from() | ||
| 61 | cause(err) | ||
| 62 | } | ||
| 63 | PackMismatch { expected: object::Id, actual: object::Id } { | ||
| 64 | display("The packfiles checksum didn't match the index file checksum: expected {}, got {}", expected, actual) | ||
| 65 | } | ||
| 57 | } | 66 | } |
| 58 | } | 67 | } |
| 59 | 68 | ||
| @@ -78,17 +87,39 @@ impl File { · 18 unmodified lines | |||
| 78 | pub fn checksum_of_index(&self) -> object::Id { | 87 | pub fn checksum_of_index(&self) -> object::Id { |
| 79 | object::Id::from_20_bytes(&self.data[self.data.len() - SHA1_SIZE..]) | 88 | object::Id::from_20_bytes(&self.data[self.data.len() - SHA1_SIZE..]) |
| 80 | } | 89 | } |
| 90 | |||
| 91 | /// If `pack` is provided, it is expected (and validated to be) the pack belonging to this index. | ||
| 92 | /// It will be used to validate internal integrity of the pack before checking each objects integrity | ||
| 93 | /// is indeed as advertised via its SHA1 as stored in this index, as well as the CRC hash. | ||
| 81 | #[cfg(any(feature = "fast-sha1", feature = "minimal-sha1"))] | 94 | #[cfg(any(feature = "fast-sha1", feature = "minimal-sha1"))] |
| 82 | pub fn verify_checksum_of_index(&self) -> Result<object::Id, ChecksumError> { | 95 | pub fn verify_checksum_of_index( |
| 83 | let mut hasher = crate::sha1::Sha1::default(); | 96 | &self, |
| 84 | hasher.update(&self.data[..self.data.len() - SHA1_SIZE]); | 97 | pack: Option<&pack::File>, |
| 85 | let actual = hasher.digest(); | 98 | ) -> Result<object::Id, ChecksumError> { |
| 86 | 99 | let verify_self = || { | |
| 87 | let expected = self.checksum_of_index(); | 100 | let mut hasher = crate::sha1::Sha1::default(); |
| 88 | if actual == expected { | 101 | hasher.update(&self.data[..self.data.len() - SHA1_SIZE]); |
| 89 | Ok(actual) | 102 | let actual = hasher.digest(); |
| 90 | } else { | 103 | |
| 91 | Err(ChecksumError::Mismatch { actual, expected }) | 104 | let expected = self.checksum_of_index(); |
| 105 | if actual == expected { | ||
| 106 | Ok(actual) | ||
| 107 | } else { | ||
| 108 | Err(ChecksumError::Mismatch { actual, expected }) | ||
| 109 | } | ||
| 110 | }; | ||
| 111 | match pack { | ||
| 112 | None => verify_self(), | ||
| 113 | Some(pack) => { | ||
| 114 | if self.checksum_of_pack() != pack.checksum() { | ||
| 115 | return Err(ChecksumError::PackMismatch { | ||
| 116 | actual: pack.checksum(), | ||
| 117 | expected: self.checksum_of_pack(), | ||
| 118 | }); | ||
| 119 | } | ||
| 120 | pack.verify_checksum()?; | ||
| 121 | verify_self() | ||
| 122 | } | ||
| 92 | } | 123 | } |
| 93 | } | 124 | } |
| 94 | 125 | ||
| @@ -24,7 +24,7 @@ fn pack_lookup() { · 23 unmodified lines | |||
| 24 | assert_eq!(pack.kind(), pack::Kind::V2); | 24 | assert_eq!(pack.kind(), pack::Kind::V2); |
| 25 | assert_eq!(pack.num_objects(), idx.num_objects()); | 25 | assert_eq!(pack.num_objects(), idx.num_objects()); |
| 26 | assert_eq!( | 26 | assert_eq!( |
| 27 | idx.verify_checksum_of_index().unwrap(), | 27 | idx.verify_checksum_of_index(Some(&pack)).unwrap(), |
| 28 | idx.checksum_of_index() | 28 | idx.checksum_of_index() |
| 29 | ); | 29 | ); |
| 30 | for idx_entry in idx.iter() { | 30 | for idx_entry in idx.iter() { |
| @@ -66,6 +66,10 @@ fn iter() { · 35 unmodified lines | |||
| 66 | assert_eq!(idx.kind(), *kind); | 66 | assert_eq!(idx.kind(), *kind); |
| 67 | assert_eq!(idx.version(), *version); | 67 | assert_eq!(idx.version(), *version); |
| 68 | assert_eq!(idx.num_objects(), *num_objects); | 68 | assert_eq!(idx.num_objects(), *num_objects); |
| 69 | assert_eq!( | ||
| 70 | idx.verify_checksum_of_index(None).unwrap(), | ||
| 71 | idx.checksum_of_index() | ||
| 72 | ); | ||
| 69 | assert_eq!(idx.checksum_of_index(), hex_to_id(index_checksum)); | 73 | assert_eq!(idx.checksum_of_index(), hex_to_id(index_checksum)); |
| 70 | assert_eq!(idx.checksum_of_pack(), hex_to_id(pack_checksum)); | 74 | assert_eq!(idx.checksum_of_pack(), hex_to_id(pack_checksum)); |
| 71 | assert_eq!(idx.iter().count(), *num_objects as usize); | 75 | assert_eq!(idx.iter().count(), *num_objects as usize); |
| @@ -5,7 +5,11 @@ pub fn init() -> Result<()> { · 4 unmodified lines | |||
| 5 | git_repository::init::repository().with_context(|| "Repository initialization failed") | 5 | git_repository::init::repository().with_context(|| "Repository initialization failed") |
| 6 | } | 6 | } |
| 7 | 7 | ||
| 8 | pub fn verify_pack_or_pack_index(path: impl AsRef<Path>, mut out: impl io::Write) -> Result<()> { | 8 | pub fn verify_pack_or_pack_index( |
| 9 | path: impl AsRef<Path>, | ||
| 10 | mut out: impl io::Write, | ||
| 11 | mut err: impl io::Write, | ||
| 12 | ) -> Result<()> { | ||
| 9 | let path = path.as_ref(); | 13 | let path = path.as_ref(); |
| 10 | let ext = path.extension() | 14 | let ext = path.extension() |
| 11 | .and_then(|ext| ext.to_str()) | 15 | .and_then(|ext| ext.to_str()) |
| @@ -18,7 +22,14 @@ pub fn verify_pack_or_pack_index(path: impl AsRef<Path>, mut out: impl io::Write · 6 unmodified lines | |||
| 18 | "idx" => { | 22 | "idx" => { |
| 19 | let idx = git_odb::pack::index::File::at(path) | 23 | let idx = git_odb::pack::index::File::at(path) |
| 20 | .with_context(|| "Could not open pack index file")?; | 24 | .with_context(|| "Could not open pack index file")?; |
| 21 | idx.verify_checksum_of_index()?; | 25 | let packfile_path = path.with_extension("pack"); |
| 26 | let pack = git_odb::pack::File::at(&packfile_path) | ||
| 27 | .or_else(|e| { | ||
| 28 | writeln!(err, "Could not find matching pack file at '{}' - only index file will be verified, error was: {}", packfile_path.display(), e).ok(); | ||
| 29 | Err(e) | ||
| 30 | }) | ||
| 31 | .ok(); | ||
| 32 | idx.verify_checksum_of_index(pack.as_ref())?; | ||
| 22 | } | 33 | } |
| 23 | ext => { | 34 | ext => { |
| 24 | return Err(anyhow!( | 35 | return Err(anyhow!( |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | use anyhow::Result; | 3 | use anyhow::Result; |
| 4 | use gitoxide_core as core; | 4 | use gitoxide_core as core; |
| 5 | use std::io::stdout; | 5 | use std::io::{stderr, stdout}; |
| 6 | use structopt::StructOpt; | 6 | use structopt::StructOpt; |
| 7 | 7 | ||
| 8 | mod options { | 8 | mod options { |
| @@ -53,7 +53,7 @@ fn main() -> Result<()> { · 44 unmodified lines | |||
| 53 | options::Subcommands::Init => core::init(), | 53 | options::Subcommands::Init => core::init(), |
| 54 | options::Subcommands::Plumbing(cmd) => match cmd { | 54 | options::Subcommands::Plumbing(cmd) => match cmd { |
| 55 | options::Plumbing::VerifyPack { path } => { | 55 | options::Plumbing::VerifyPack { path } => { |
| 56 | core::verify_pack_or_pack_index(path, stdout()) | 56 | core::verify_pack_or_pack_index(path, stdout(), stderr()) |
| 57 | } | 57 | } |
| 58 | }, | 58 | }, |
| 59 | }?; | 59 | }?; |