Skip to content

Commit b062bec

Browse files
committed
Assert store tree cache matches actual source objects (#293)
1 parent d89a587 commit b062bec

File tree

3 files changed

+68
-18
lines changed

3 files changed

+68
-18
lines changed

git-index/src/extension/tree.rs

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ pub mod verify {
1717
quick_error! {
1818
#[derive(Debug)]
1919
pub enum Error {
20+
MissingTreeDirectory { parent_id: git_hash::ObjectId, entry_id: git_hash::ObjectId, name: BString } {
21+
display("The entry {} at path '{}' in parent tree {} wasn't found in the nodes children, making it incomplete", entry_id, name, parent_id)
22+
}
23+
TreeNodeNotFound { oid: git_hash::ObjectId } {
24+
display("The tree with id {} wasn't found in the object database", oid)
25+
}
26+
TreeNodeChildcountMismatch { oid: git_hash::ObjectId, expected_childcount: usize, actual_childcount: usize } {
27+
display("The tree with id {} should have {} children, but its cached representation had {} of them", oid, expected_childcount, actual_childcount)
28+
}
2029
RootWithName { name: BString } {
2130
display("The root tree was named '{}', even though it should be empty", name)
2231
}
@@ -31,23 +40,25 @@ pub mod verify {
3140
}
3241

3342
impl Tree {
34-
pub fn verify(&self) -> Result<(), verify::Error> {
35-
fn verify_recursive(parent_id: git_hash::ObjectId, children: &[Tree]) -> Result<Option<u32>, verify::Error> {
43+
pub fn verify<F>(&self, use_find: bool, mut find: F) -> Result<(), verify::Error>
44+
where
45+
F: for<'a> FnMut(&git_hash::oid, &'a mut Vec<u8>) -> Option<git_object::TreeRefIter<'a>>,
46+
{
47+
fn verify_recursive<F>(
48+
parent_id: git_hash::ObjectId,
49+
children: &[Tree],
50+
mut find_buf: Option<&mut Vec<u8>>,
51+
find: &mut F,
52+
) -> Result<Option<u32>, verify::Error>
53+
where
54+
F: for<'a> FnMut(&git_hash::oid, &'a mut Vec<u8>) -> Option<git_object::TreeRefIter<'a>>,
55+
{
3656
if children.is_empty() {
3757
return Ok(None);
3858
}
3959
let mut entries = 0;
4060
let mut prev = None::<&Tree>;
4161
for child in children {
42-
let actual_num_entries = verify_recursive(child.id, &child.children)?;
43-
if let Some(actual) = actual_num_entries {
44-
if actual > child.num_entries {
45-
return Err(verify::Error::EntriesCount {
46-
actual,
47-
expected: child.num_entries,
48-
});
49-
}
50-
}
5162
entries += child.num_entries;
5263
if let Some(prev) = prev {
5364
if prev.name.cmp(&child.name) != Ordering::Less {
@@ -60,6 +71,43 @@ impl Tree {
6071
}
6172
prev = Some(child);
6273
}
74+
if let Some(buf) = find_buf.as_mut() {
75+
let tree_entries =
76+
find(&parent_id, *buf).ok_or_else(|| verify::Error::TreeNodeNotFound { oid: parent_id })?;
77+
let mut num_entries = 0;
78+
for entry in tree_entries
79+
.filter_map(Result::ok)
80+
.filter(|e| e.mode == git_object::tree::EntryMode::Tree)
81+
{
82+
children
83+
.binary_search_by(|e| e.name.as_bstr().cmp(&entry.filename))
84+
.map_err(|_| verify::Error::MissingTreeDirectory {
85+
parent_id,
86+
entry_id: entry.oid.to_owned(),
87+
name: entry.filename.to_owned(),
88+
})?;
89+
num_entries += 1;
90+
}
91+
92+
if num_entries != children.len() {
93+
return Err(verify::Error::TreeNodeChildcountMismatch {
94+
oid: parent_id,
95+
expected_childcount: num_entries,
96+
actual_childcount: children.len(),
97+
});
98+
}
99+
}
100+
for child in children {
101+
let actual_num_entries = verify_recursive(child.id, &child.children, find_buf.as_deref_mut(), find)?;
102+
if let Some(actual) = actual_num_entries {
103+
if actual > child.num_entries {
104+
return Err(verify::Error::EntriesCount {
105+
actual,
106+
expected: child.num_entries,
107+
});
108+
}
109+
}
110+
}
63111
Ok(entries.into())
64112
}
65113

@@ -69,7 +117,8 @@ impl Tree {
69117
});
70118
}
71119

72-
let declared_entries = verify_recursive(self.id, &self.children)?;
120+
let mut buf = Vec::new();
121+
let declared_entries = verify_recursive(self.id, &self.children, use_find.then(|| &mut buf), &mut find)?;
73122
if let Some(actual) = declared_entries {
74123
if actual > self.num_entries {
75124
return Err(verify::Error::EntriesCount {

git-index/src/verify.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub mod extensions {
1919
use crate::extension;
2020
use quick_error::quick_error;
2121

22-
pub fn no_find<'a>(_: &git_hash::oid, _: &'a mut Vec<u8>) -> Option<git_object::TreeRef<'a>> {
22+
pub fn no_find<'a>(_: &git_hash::oid, _: &'a mut Vec<u8>) -> Option<git_object::TreeRefIter<'a>> {
2323
None
2424
}
2525

@@ -56,11 +56,11 @@ impl State {
5656
}
5757

5858
/// Note: `find` cannot be `Option<F>` as we can't call it with a closure then due to the indirection through `Some`.
59-
pub fn verify_extensions<F>(&self, _use_find: bool, _find: F) -> Result<(), extensions::Error>
59+
pub fn verify_extensions<F>(&self, use_find: bool, find: F) -> Result<(), extensions::Error>
6060
where
61-
F: for<'a> FnMut(&git_hash::oid, &'a mut Vec<u8>) -> Option<git_object::TreeRef<'a>>,
61+
F: for<'a> FnMut(&git_hash::oid, &'a mut Vec<u8>) -> Option<git_object::TreeRefIter<'a>>,
6262
{
63-
self.tree().map(|t| t.verify()).transpose()?;
63+
self.tree().map(|t| t.verify(use_find, find)).transpose()?;
6464
// TODO: verify links by running the whole set of tests on the index
6565
// - do that once we load it as well, or maybe that's lazy loaded? Too many questions for now.
6666
Ok(())

gitoxide-core/src/repository.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub mod verify {
4343
) -> anyhow::Result<()> {
4444
let repo = git_repository::open(repo)?;
4545
#[cfg_attr(not(feature = "serde1"), allow(unused))]
46-
let outcome = repo.objects.verify_integrity(
46+
let mut outcome = repo.objects.verify_integrity(
4747
progress,
4848
should_interrupt,
4949
git_repository::odb::pack::index::verify::integrity::Options {
@@ -61,8 +61,9 @@ pub mod verify {
6161
index.verify_extensions(true, {
6262
use git::odb::FindExt;
6363
let handle = repo.objects.to_handle();
64-
move |oid, buf: &mut Vec<u8>| handle.find_tree(oid, buf).ok()
64+
move |oid, buf: &mut Vec<u8>| handle.find_tree_iter(oid, buf).ok()
6565
})?;
66+
outcome.progress.info(format!("Index at '{}' OK", index.path.display()));
6667
}
6768
match output_statistics {
6869
Some(OutputFormat::Human) => writeln!(out, "Human output is currently unsupported, use JSON instead")?,

0 commit comments

Comments
 (0)