diff --git a/git-odb/src/pack/index/file.rs b/git-odb/src/pack/index/file.rs index 7c4648687bc..4c2794eae56 100644 --- a/git-odb/src/pack/index/file.rs +++ b/git-odb/src/pack/index/file.rs @@ -1,3 +1,4 @@ +use crate::pack; use byteorder::{BigEndian, ByteOrder}; use filebuffer::FileBuffer; use git_object::{self as object, SHA1_SIZE}; @@ -54,6 +55,14 @@ quick_error! { Mismatch { expected: object::Id, actual: object::Id } { display("index checksum mismatch: expected {}, got {}", expected, actual) } + PackChecksum (err: pack::ChecksumError) { + display("The pack of this index file failed to verify its checksums") + from() + cause(err) + } + PackMismatch { expected: object::Id, actual: object::Id } { + display("The packfiles checksum didn't match the index file checksum: expected {}, got {}", expected, actual) + } } } @@ -78,17 +87,39 @@ impl File { pub fn checksum_of_index(&self) -> object::Id { object::Id::from_20_bytes(&self.data[self.data.len() - SHA1_SIZE..]) } + + /// If `pack` is provided, it is expected (and validated to be) the pack belonging to this index. + /// It will be used to validate internal integrity of the pack before checking each objects integrity + /// is indeed as advertised via its SHA1 as stored in this index, as well as the CRC hash. #[cfg(any(feature = "fast-sha1", feature = "minimal-sha1"))] - pub fn verify_checksum_of_index(&self) -> Result { - let mut hasher = crate::sha1::Sha1::default(); - hasher.update(&self.data[..self.data.len() - SHA1_SIZE]); - let actual = hasher.digest(); - - let expected = self.checksum_of_index(); - if actual == expected { - Ok(actual) - } else { - Err(ChecksumError::Mismatch { actual, expected }) + pub fn verify_checksum_of_index( + &self, + pack: Option<&pack::File>, + ) -> Result { + let verify_self = || { + let mut hasher = crate::sha1::Sha1::default(); + hasher.update(&self.data[..self.data.len() - SHA1_SIZE]); + let actual = hasher.digest(); + + let expected = self.checksum_of_index(); + if actual == expected { + Ok(actual) + } else { + Err(ChecksumError::Mismatch { actual, expected }) + } + }; + match pack { + None => verify_self(), + Some(pack) => { + if self.checksum_of_pack() != pack.checksum() { + return Err(ChecksumError::PackMismatch { + actual: pack.checksum(), + expected: self.checksum_of_pack(), + }); + } + pack.verify_checksum()?; + verify_self() + } } } diff --git a/git-odb/tests/pack/index.rs b/git-odb/tests/pack/index.rs index 66ff5178855..74704092a01 100644 --- a/git-odb/tests/pack/index.rs +++ b/git-odb/tests/pack/index.rs @@ -24,7 +24,7 @@ fn pack_lookup() { assert_eq!(pack.kind(), pack::Kind::V2); assert_eq!(pack.num_objects(), idx.num_objects()); assert_eq!( - idx.verify_checksum_of_index().unwrap(), + idx.verify_checksum_of_index(Some(&pack)).unwrap(), idx.checksum_of_index() ); for idx_entry in idx.iter() { @@ -66,6 +66,10 @@ fn iter() { assert_eq!(idx.kind(), *kind); assert_eq!(idx.version(), *version); assert_eq!(idx.num_objects(), *num_objects); + assert_eq!( + idx.verify_checksum_of_index(None).unwrap(), + idx.checksum_of_index() + ); assert_eq!(idx.checksum_of_index(), hex_to_id(index_checksum)); assert_eq!(idx.checksum_of_pack(), hex_to_id(pack_checksum)); assert_eq!(idx.iter().count(), *num_objects as usize); diff --git a/gitoxide-core/src/lib.rs b/gitoxide-core/src/lib.rs index 56ac9be6bf8..7d02f9f02c2 100644 --- a/gitoxide-core/src/lib.rs +++ b/gitoxide-core/src/lib.rs @@ -5,7 +5,11 @@ pub fn init() -> Result<()> { git_repository::init::repository().with_context(|| "Repository initialization failed") } -pub fn verify_pack_or_pack_index(path: impl AsRef, mut out: impl io::Write) -> Result<()> { +pub fn verify_pack_or_pack_index( + path: impl AsRef, + mut out: impl io::Write, + mut err: impl io::Write, +) -> Result<()> { let path = path.as_ref(); let ext = path.extension() .and_then(|ext| ext.to_str()) @@ -18,7 +22,14 @@ pub fn verify_pack_or_pack_index(path: impl AsRef, mut out: impl io::Write "idx" => { let idx = git_odb::pack::index::File::at(path) .with_context(|| "Could not open pack index file")?; - idx.verify_checksum_of_index()?; + let packfile_path = path.with_extension("pack"); + let pack = git_odb::pack::File::at(&packfile_path) + .or_else(|e| { + writeln!(err, "Could not find matching pack file at '{}' - only index file will be verified, error was: {}", packfile_path.display(), e).ok(); + Err(e) + }) + .ok(); + idx.verify_checksum_of_index(pack.as_ref())?; } ext => { return Err(anyhow!( diff --git a/src/main.rs b/src/main.rs index 925a9ac0b55..3488d9fbacb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use anyhow::Result; use gitoxide_core as core; -use std::io::stdout; +use std::io::{stderr, stdout}; use structopt::StructOpt; mod options { @@ -53,7 +53,7 @@ fn main() -> Result<()> { options::Subcommands::Init => core::init(), options::Subcommands::Plumbing(cmd) => match cmd { options::Plumbing::VerifyPack { path } => { - core::verify_pack_or_pack_index(path, stdout()) + core::verify_pack_or_pack_index(path, stdout(), stderr()) } }, }?;