Skip to content

Commit f93febe

Browse files
committed
fix tree ext reading and writing; round-trip with long path works now
1 parent 581cbd7 commit f93febe

File tree

10 files changed

+73
-41
lines changed

10 files changed

+73
-41
lines changed

Cargo.lock

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

git-index/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ bstr = { version = "0.2.13", default-features = false }
4545
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] }
4646
smallvec = "1.7.0"
4747
atoi = "1.0.0"
48+
itoa = "1.0.3"
4849
bitflags = "1.3.2"
4950

5051
document-features = { version = "0.2.0", optional = true }

git-index/src/extension/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ pub struct Tree {
2626
pub id: git_hash::ObjectId,
2727
/// The amount of non-tree items in this directory tree, including sub-trees, recursively.
2828
/// The value of the top-level tree is thus equal to the value of the total amount of entries.
29-
pub num_entries: u32,
29+
/// If `None`, the tree is considered invalid and needs to be refreshed
30+
pub num_entries: Option<u32>,
3031
/// The child-trees below the current tree.
3132
pub children: Vec<Tree>,
3233
}

git-index/src/extension/tree/decode.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::extension::Tree;
22
use crate::util::{split_at_byte_exclusive, split_at_pos};
33
use git_hash::ObjectId;
4+
use std::convert::TryInto;
45

56
/// A recursive data structure
67
pub fn decode(data: &[u8], object_hash: git_hash::Kind) -> Option<Tree> {
@@ -17,13 +18,20 @@ fn one_recursive(data: &[u8], hash_len: usize) -> Option<(Tree, &[u8])> {
1718
let (path, data) = split_at_byte_exclusive(data, 0)?;
1819

1920
let (entry_count, data) = split_at_byte_exclusive(data, b' ')?;
20-
let num_entries: u32 = atoi::atoi(entry_count)?;
21+
let num_entries: i32 = atoi::atoi(entry_count)?;
2122

2223
let (subtree_count, data) = split_at_byte_exclusive(data, b'\n')?;
2324
let subtree_count: usize = atoi::atoi(subtree_count)?;
2425

25-
let (hash, mut data) = split_at_pos(data, hash_len)?;
26-
let id = ObjectId::from(hash);
26+
let (id, mut data) = if num_entries >= 0 {
27+
let (hash, data) = split_at_pos(data, hash_len)?;
28+
(ObjectId::from(hash), data)
29+
} else {
30+
(
31+
ObjectId::null(git_hash::Kind::from_hex_len(hash_len * 2).expect("valid hex_len")),
32+
data,
33+
)
34+
};
2735

2836
let mut subtrees = Vec::with_capacity(subtree_count);
2937
for _ in 0..subtree_count {
@@ -42,7 +50,7 @@ fn one_recursive(data: &[u8], hash_len: usize) -> Option<(Tree, &[u8])> {
4250
Some((
4351
Tree {
4452
id,
45-
num_entries,
53+
num_entries: num_entries.try_into().ok(),
4654
name: path.into(),
4755
children: subtrees,
4856
},

git-index/src/extension/tree/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ mod tests {
1616

1717
#[test]
1818
fn size_of_tree() {
19-
assert_eq!(std::mem::size_of::<crate::extension::Tree>(), 80);
19+
assert_eq!(std::mem::size_of::<crate::extension::Tree>(), 88);
2020
}
2121
}

git-index/src/extension/tree/verify.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ impl Tree {
5757
let mut entries = 0;
5858
let mut prev = None::<&Tree>;
5959
for child in children {
60-
entries += child.num_entries;
60+
entries += child.num_entries.unwrap_or(0);
6161
if let Some(prev) = prev {
6262
if prev.name.cmp(&child.name) != Ordering::Less {
6363
return Err(Error::OutOfOrder {
@@ -98,11 +98,11 @@ impl Tree {
9898
// This is actually needed here as it's a mut ref, which isn't copy. We do a re-borrow here.
9999
#[allow(clippy::needless_option_as_deref)]
100100
let actual_num_entries = verify_recursive(child.id, &child.children, find_buf.as_deref_mut(), find)?;
101-
if let Some(actual) = actual_num_entries {
102-
if actual > child.num_entries {
101+
if let Some((actual, num_entries)) = actual_num_entries.zip(child.num_entries) {
102+
if actual > num_entries {
103103
return Err(Error::EntriesCount {
104104
actual,
105-
expected: child.num_entries,
105+
expected: num_entries,
106106
});
107107
}
108108
}
@@ -118,11 +118,11 @@ impl Tree {
118118

119119
let mut buf = Vec::new();
120120
let declared_entries = verify_recursive(self.id, &self.children, use_find.then(|| &mut buf), &mut find)?;
121-
if let Some(actual) = declared_entries {
122-
if actual > self.num_entries {
121+
if let Some((actual, num_entries)) = declared_entries.zip(self.num_entries) {
122+
if actual > num_entries {
123123
return Err(Error::EntriesCount {
124124
actual,
125-
expected: self.num_entries,
125+
expected: num_entries,
126126
});
127127
}
128128
}

git-index/src/extension/tree/write.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@ impl Tree {
55
/// Serialize this instance to `out`.
66
pub fn write_to(&self, mut out: impl std::io::Write) -> Result<(), std::io::Error> {
77
fn tree_entry(out: &mut impl std::io::Write, tree: &Tree) -> Result<(), std::io::Error> {
8-
let num_entries_ascii = tree.num_entries.to_string();
9-
let num_children_ascii = tree.children.len().to_string();
8+
let mut buf = itoa::Buffer::new();
9+
let num_entries = match tree.num_entries {
10+
Some(num_entries) => buf.format(num_entries),
11+
None => buf.format(-1),
12+
};
1013

1114
out.write_all(tree.name.as_slice())?;
1215
out.write_all(b"\0")?;
13-
out.write_all(num_entries_ascii.as_bytes())?;
16+
out.write_all(num_entries.as_bytes())?;
1417
out.write_all(b" ")?;
15-
out.write_all(num_children_ascii.as_bytes())?;
18+
let num_children = buf.format(tree.children.len());
19+
out.write_all(num_children.as_bytes())?;
1620
out.write_all(b"\n")?;
17-
out.write_all(tree.id.as_bytes())?;
21+
if tree.num_entries.is_some() {
22+
out.write_all(tree.id.as_bytes())?;
23+
}
1824

1925
for child in &tree.children {
2026
tree_entry(out, child)?;
@@ -25,7 +31,7 @@ impl Tree {
2531

2632
let signature = tree::SIGNATURE;
2733

28-
let estimated_size = self.num_entries * (300 + 3 + 1 + 3 + 1 + 20);
34+
let estimated_size = self.num_entries.unwrap_or(0) * (300 + 3 + 1 + 3 + 1 + 20);
2935
let mut entries: Vec<u8> = Vec::with_capacity(estimated_size as usize);
3036
tree_entry(&mut entries, self)?;
3137

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ fn v2_with_single_entry_tree_and_eoie_ext() {
5252
assert_eq!(entry.path(&file.state), "a");
5353

5454
let tree = file.tree().unwrap();
55-
assert_eq!(tree.num_entries, 1);
55+
assert_eq!(tree.num_entries.unwrap_or_default(), 1);
5656
assert_eq!(tree.id, hex_to_id("496d6428b9cf92981dc9495211e6e1120fb6f2ba"));
5757
assert!(tree.name.is_empty());
5858
assert!(tree.children.is_empty());
@@ -64,7 +64,7 @@ fn v2_empty() {
6464
assert_eq!(file.version(), Version::V2);
6565
assert_eq!(file.entries().len(), 0);
6666
let tree = file.tree().unwrap();
67-
assert_eq!(tree.num_entries, 0);
67+
assert_eq!(tree.num_entries.unwrap_or_default(), 0);
6868
assert!(tree.name.is_empty());
6969
assert!(tree.children.is_empty());
7070
assert_eq!(tree.id, hex_to_id("4b825dc642cb6eb9a060e54bf8d69288fbee4904"));
@@ -86,13 +86,13 @@ fn v2_with_multiple_entries_without_eoie_ext() {
8686

8787
let tree = file.tree().unwrap();
8888
assert_eq!(tree.id, hex_to_id("c9b29c3168d8e677450cc650238b23d9390801fb"));
89-
assert_eq!(tree.num_entries, 6);
89+
assert_eq!(tree.num_entries.unwrap_or_default(), 6);
9090
assert!(tree.name.is_empty());
9191
assert_eq!(tree.children.len(), 1);
9292

9393
let tree = &tree.children[0];
9494
assert_eq!(tree.id, hex_to_id("765b32c65d38f04c4f287abda055818ec0f26912"));
95-
assert_eq!(tree.num_entries, 3);
95+
assert_eq!(tree.num_entries.unwrap_or_default(), 3);
9696
assert_eq!(tree.name.as_bstr(), "d");
9797
}
9898

@@ -143,6 +143,15 @@ fn v2_very_long_path() {
143143
.chain(std::iter::once('q'))
144144
.collect::<String>()
145145
);
146+
assert!(
147+
file.tree().is_some(),
148+
"Tree has invalid entries, but that shouldn't prevent us from loading it"
149+
);
150+
let tree = file.tree().expect("present");
151+
assert_eq!(tree.num_entries, None, "root tree has invalid entries actually");
152+
assert_eq!(tree.name.as_bstr(), "");
153+
assert_eq!(tree.num_entries, None, "it's marked invalid actually");
154+
assert!(tree.id.is_null(), "there is no id for the root")
146155
}
147156

148157
#[test]

git-index/tests/index/file/write.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,18 @@ fn roundtrips() -> crate::Result {
1212
}
1313
use Kind::*;
1414
let input = [
15-
(Loose("very-long-path"), write::Options::default(), false), // unclear why the file is smaller when written back
16-
(Generated("v2"), write::Options::default(), true),
17-
(Generated("V2_empty"), write::Options::default(), true),
15+
(
16+
Loose("very-long-path"),
17+
write::Options {
18+
extensions: write::Extensions::Given {
19+
end_of_index_entry: false,
20+
tree_cache: true,
21+
},
22+
..write::Options::default()
23+
},
24+
), // tree extension can't be read
25+
(Generated("v2"), write::Options::default()),
26+
(Generated("V2_empty"), write::Options::default()),
1827
(
1928
Generated("v2_more_files"),
2029
write::Options {
@@ -24,11 +33,10 @@ fn roundtrips() -> crate::Result {
2433
},
2534
..write::Options::default()
2635
},
27-
true,
2836
),
2937
];
3038

31-
for (fixture, options, compare_byte_by_byte) in input {
39+
for (fixture, options) in input {
3240
let (path, fixture) = match fixture {
3341
Generated(name) => (crate::fixture_index_path(name), name),
3442
Loose(name) => (loose_file_path(name), name),
@@ -41,9 +49,7 @@ fn roundtrips() -> crate::Result {
4149
let (actual, _) = State::from_bytes(&out_bytes, FileTime::now(), decode::Options::default())?;
4250

4351
compare_states(&actual, &expected, options, fixture);
44-
if compare_byte_by_byte {
45-
compare_raw_bytes(&out_bytes, &expected_bytes, fixture);
46-
}
52+
compare_raw_bytes(&out_bytes, &expected_bytes, fixture);
4753
}
4854
Ok(())
4955
}
@@ -179,7 +185,7 @@ fn compare_raw_bytes(generated: &[u8], expected: &[u8], fixture: &str) {
179185
let expected = &expected[range_left..range_right];
180186

181187
panic! {"\n\nRoundtrip failed for index in fixture {:?} at position {:?}\n\
182-
\t Input: ... {:?} ...\n\
188+
\t Actual: ... {:?} ...\n\
183189
\tExpected: ... {:?} ...\n\n\
184190
", &fixture, index, generated, expected}
185191
}

gitoxide-core/src/index/information.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ mod serde_only {
1414
/// The id of the directory tree of the associated tree object.
1515
id: String,
1616
/// The amount of non-tree entries contained within, and definitely not zero.
17-
num_entries: u32,
17+
num_entries: Option<u32>,
1818
children: Vec<Tree>,
1919
}
2020

0 commit comments

Comments
 (0)