Skip to content

Commit b310d04

Browse files
committed
fix!: skip the null-hash when validating the index.
This is needed for compatibility with `index.skipHash`, which may skip producing the hash at the end of the index file, just filling in the null-hash.
1 parent e2c0912 commit b310d04

File tree

5 files changed

+50
-27
lines changed

5 files changed

+50
-27
lines changed

gix-index/src/decode/mod.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub struct Options {
5454

5555
impl State {
5656
/// Decode an index state from `data` and store `timestamp` in the resulting instance for pass-through, assuming `object_hash`
57-
/// to be used through the file.
57+
/// to be used through the file. Also return the stored hash over all bytes in `data` or `None` if none was written due to `index.skipHash`.
5858
pub fn from_bytes(
5959
data: &[u8],
6060
timestamp: FileTime,
@@ -64,7 +64,7 @@ impl State {
6464
min_extension_block_in_bytes_for_threading,
6565
expected_checksum,
6666
}: Options,
67-
) -> Result<(Self, gix_hash::ObjectId), Error> {
67+
) -> Result<(Self, Option<gix_hash::ObjectId>), Error> {
6868
let _span = gix_features::trace::detail!("gix_index::State::from_bytes()");
6969
let (version, num_entries, post_header_data) = header::decode(data, object_hash)?;
7070
let start_of_extensions = extension::end_of_index_entry::decode(data, object_hash);
@@ -214,10 +214,11 @@ impl State {
214214
}
215215

216216
let checksum = gix_hash::ObjectId::from(data);
217-
if let Some(expected_checksum) = expected_checksum {
218-
if checksum != expected_checksum {
217+
let checksum = (!checksum.is_null()).then_some(checksum);
218+
if let Some((expected_checksum, actual_checksum)) = expected_checksum.zip(checksum) {
219+
if actual_checksum != expected_checksum {
219220
return Err(Error::ChecksumMismatch {
220-
actual_checksum: checksum,
221+
actual_checksum,
221222
expected_checksum,
222223
});
223224
}

gix-index/src/file/init.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ impl File {
4545
}
4646

4747
/// Open an index file at `path` with `options`, assuming `object_hash` is used throughout the file.
48+
///
49+
/// Note that the verification of the file hash depends on `options`, and even then it's performed after the file was read and not
50+
/// before it is read. That way, invalid files would see a more descriptive error message as we try to parse them.
4851
pub fn at(path: impl Into<PathBuf>, object_hash: gix_hash::Kind, options: decode::Options) -> Result<Self, Error> {
4952
let _span = gix_features::trace::detail!("gix_index::File::at()");
5053
let path = path.into();
@@ -57,11 +60,7 @@ impl File {
5760
};
5861

5962
let (state, checksum) = State::from_bytes(&data, mtime, object_hash, options)?;
60-
let mut file = File {
61-
state,
62-
path,
63-
checksum: Some(checksum),
64-
};
63+
let mut file = File { state, path, checksum };
6564
if let Some(mut link) = file.link.take() {
6665
link.dissolve_into(&mut file, object_hash, options)?;
6766
}
@@ -72,7 +71,7 @@ impl File {
7271
/// Consume `state` and pretend it was read from `path`, setting our checksum to `null`.
7372
///
7473
/// `File` instances created like that should be written to disk to set the correct checksum via `[File::write()]`.
75-
pub fn from_state(state: crate::State, path: impl Into<PathBuf>) -> Self {
74+
pub fn from_state(state: State, path: impl Into<PathBuf>) -> Self {
7675
File {
7776
state,
7877
path: path.into(),

gix-index/src/file/verify.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ mod error {
1414
actual: gix_hash::ObjectId,
1515
expected: gix_hash::ObjectId,
1616
},
17-
#[error("Checksum of in-memory index wasn't computed yet")]
18-
NoChecksum,
1917
}
2018
}
2119
pub use error::Error;
@@ -24,19 +22,22 @@ impl File {
2422
/// Verify the integrity of the index to assure its consistency.
2523
pub fn verify_integrity(&self) -> Result<(), Error> {
2624
let _span = gix_features::trace::coarse!("gix_index::File::verify_integrity()");
27-
let checksum = self.checksum.ok_or(Error::NoChecksum)?;
28-
let num_bytes_to_hash = self.path.metadata()?.len() - checksum.as_bytes().len() as u64;
29-
let should_interrupt = AtomicBool::new(false);
30-
let actual = gix_features::hash::bytes_of_file(
31-
&self.path,
32-
num_bytes_to_hash as usize,
33-
checksum.kind(),
34-
&mut gix_features::progress::Discard,
35-
&should_interrupt,
36-
)?;
37-
(actual == checksum).then_some(()).ok_or(Error::ChecksumMismatch {
38-
actual,
39-
expected: checksum,
40-
})
25+
if let Some(checksum) = self.checksum {
26+
let num_bytes_to_hash = self.path.metadata()?.len() - checksum.as_bytes().len() as u64;
27+
let should_interrupt = AtomicBool::new(false);
28+
let actual = gix_features::hash::bytes_of_file(
29+
&self.path,
30+
num_bytes_to_hash as usize,
31+
checksum.kind(),
32+
&mut gix_features::progress::Discard,
33+
&should_interrupt,
34+
)?;
35+
(actual == checksum).then_some(()).ok_or(Error::ChecksumMismatch {
36+
actual,
37+
expected: checksum,
38+
})
39+
} else {
40+
Ok(())
41+
}
4142
}
4243
}
Binary file not shown.

gix-index/tests/index/file/read.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,28 @@ fn v2_empty() {
7575
assert!(tree.name.is_empty());
7676
assert!(tree.children.is_empty());
7777
assert_eq!(tree.id, hex_to_id("4b825dc642cb6eb9a060e54bf8d69288fbee4904"));
78+
assert_eq!(
79+
file.checksum(),
80+
Some(hex_to_id("72d53f787d86a932a25a8537cee236d81846a8f1")),
81+
"checksums are read but not validated by default"
82+
);
83+
}
84+
85+
#[test]
86+
fn v2_empty_skip_hash() {
87+
let file = loose_file("skip_hash");
88+
assert_eq!(file.version(), Version::V2);
89+
assert_eq!(file.entries().len(), 0);
90+
let tree = file.tree().unwrap();
91+
assert_eq!(tree.num_entries.unwrap_or_default(), 0);
92+
assert!(tree.name.is_empty());
93+
assert!(tree.children.is_empty());
94+
assert_eq!(tree.id, hex_to_id("4b825dc642cb6eb9a060e54bf8d69288fbee4904"));
95+
assert_eq!(
96+
file.checksum(),
97+
None,
98+
"unset checksums are represented in the type system"
99+
);
78100
}
79101

80102
#[test]

0 commit comments

Comments
 (0)