Skip to content

Commit 39227a9

Browse files
authored
Merge pull request #1686 from cruessler/move-lookup-entry-to-gix-object
Add lookup_entry and lookup_entry_by_path to TreeRef
2 parents e8b3b41 + d7f4991 commit 39227a9

File tree

4 files changed

+150
-1
lines changed

4 files changed

+150
-1
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-object/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ gix-hashtable = { version = "^0.6.0", path = "../gix-hashtable" }
5050
gix-validate = { version = "^0.9.2", path = "../gix-validate" }
5151
gix-actor = { version = "^0.33.1", path = "../gix-actor" }
5252
gix-date = { version = "^0.9.2", path = "../gix-date" }
53+
gix-path = { version = "^0.10.12", path = "../gix-path" }
5354
gix-utils = { version = "^0.1.13", path = "../gix-utils" }
5455

5556
itoa = "1.0.1"

gix-object/src/tree/ref_iter.rs

+71
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,77 @@ impl<'a> TreeRefIter<'a> {
88
pub fn from_bytes(data: &'a [u8]) -> TreeRefIter<'a> {
99
TreeRefIter { data }
1010
}
11+
12+
/// Follow a sequence of `path` components starting from this instance, and look them up in `odb` one by one using `buffer`
13+
/// until the last component is looked up and its tree entry is returned.
14+
///
15+
/// # Performance Notes
16+
///
17+
/// Searching tree entries is currently done in sequence, which allows the search to be allocation free. It would be possible
18+
/// to reuse a vector and use a binary search instead, which might be able to improve performance over all.
19+
/// However, a benchmark should be created first to have some data and see which trade-off to choose here.
20+
pub fn lookup_entry<I, P>(
21+
&self,
22+
odb: impl crate::Find,
23+
buffer: &'a mut Vec<u8>,
24+
path: I,
25+
) -> Result<Option<tree::Entry>, crate::find::Error>
26+
where
27+
I: IntoIterator<Item = P>,
28+
P: PartialEq<BStr>,
29+
{
30+
buffer.clear();
31+
32+
let mut path = path.into_iter().peekable();
33+
buffer.extend_from_slice(self.data);
34+
while let Some(component) = path.next() {
35+
match TreeRefIter::from_bytes(buffer)
36+
.filter_map(Result::ok)
37+
.find(|entry| component.eq(entry.filename))
38+
{
39+
Some(entry) => {
40+
if path.peek().is_none() {
41+
return Ok(Some(entry.into()));
42+
} else {
43+
let next_id = entry.oid.to_owned();
44+
let obj = odb.try_find(&next_id, buffer)?;
45+
let Some(obj) = obj else { return Ok(None) };
46+
if !obj.kind.is_tree() {
47+
return Ok(None);
48+
}
49+
}
50+
}
51+
None => return Ok(None),
52+
}
53+
}
54+
Ok(None)
55+
}
56+
57+
/// Like [`Self::lookup_entry()`], but takes any [`AsRef<Path>`](`std::path::Path`) directly via `relative_path`,
58+
/// a path relative to this tree.
59+
/// `odb` and `buffer` are used to lookup intermediate trees.
60+
///
61+
/// # Note
62+
///
63+
/// If any path component contains illformed UTF-8 and thus can't be converted to bytes on platforms which can't do so natively,
64+
/// the returned component will be empty which makes the lookup fail.
65+
pub fn lookup_entry_by_path(
66+
&self,
67+
odb: impl crate::Find,
68+
buffer: &'a mut Vec<u8>,
69+
relative_path: impl AsRef<std::path::Path>,
70+
) -> Result<Option<tree::Entry>, crate::find::Error> {
71+
use crate::bstr::ByteSlice;
72+
self.lookup_entry(
73+
odb,
74+
buffer,
75+
relative_path.as_ref().components().map(|c: std::path::Component<'_>| {
76+
gix_path::os_str_into_bstr(c.as_os_str())
77+
.unwrap_or_else(|_| "".into())
78+
.as_bytes()
79+
}),
80+
)
81+
}
1182
}
1283

1384
impl<'a> TreeRef<'a> {

gix-object/tests/object/tree/iter.rs

+77-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use gix_object::{bstr::ByteSlice, tree, tree::EntryRef, TreeRefIter};
1+
use gix_object::{
2+
bstr::ByteSlice,
3+
tree::{self, EntryRef},
4+
TreeRefIter,
5+
};
6+
use pretty_assertions::assert_eq;
27

38
use crate::{fixture_name, hex_to_id};
49

@@ -52,3 +57,74 @@ fn everything() -> crate::Result {
5257
);
5358
Ok(())
5459
}
60+
61+
mod lookup_entry {
62+
use crate::hex_to_id;
63+
use gix_object::tree::EntryKind;
64+
use utils::entry;
65+
66+
#[test]
67+
fn top_level_directory() -> crate::Result {
68+
assert_eq!(
69+
utils::lookup_entry_by_path("bin")?,
70+
entry(
71+
"bin",
72+
EntryKind::Blob,
73+
hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
74+
)
75+
);
76+
Ok(())
77+
}
78+
79+
#[test]
80+
fn nested_file() -> crate::Result {
81+
assert_eq!(
82+
utils::lookup_entry_by_path("file/a")?,
83+
entry(
84+
"a",
85+
EntryKind::Blob,
86+
hex_to_id("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")
87+
)
88+
);
89+
Ok(())
90+
}
91+
92+
#[test]
93+
fn non_existing_nested_file() -> crate::Result {
94+
for path in ["file/does-not-exist", "non-existing", "file/a/through-file"] {
95+
let actual = utils::lookup_entry_by_path(path)?;
96+
assert_eq!(actual, None);
97+
}
98+
Ok(())
99+
}
100+
101+
mod utils {
102+
use crate::hex_to_id;
103+
104+
use gix_object::{tree, FindExt};
105+
106+
pub(super) fn entry(filename: &str, mode: tree::EntryKind, oid: gix_hash::ObjectId) -> Option<tree::Entry> {
107+
Some(tree::Entry {
108+
mode: mode.into(),
109+
filename: filename.into(),
110+
oid,
111+
})
112+
}
113+
114+
pub(super) fn tree_odb() -> gix_testtools::Result<gix_odb::Handle> {
115+
let root = gix_testtools::scripted_fixture_read_only("make_trees.sh")?;
116+
Ok(gix_odb::at(root.join(".git/objects"))?)
117+
}
118+
119+
pub(super) fn lookup_entry_by_path(path: &str) -> gix_testtools::Result<Option<gix_object::tree::Entry>> {
120+
let odb = tree_odb()?;
121+
let root_tree_id = hex_to_id("ff7e7d2aecae1c3fb15054b289a4c58aa65b8646");
122+
123+
let mut buf = Vec::new();
124+
let root_tree = odb.find_tree_iter(&root_tree_id, &mut buf)?;
125+
126+
let mut buf = Vec::new();
127+
root_tree.lookup_entry_by_path(&odb, &mut buf, path)
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)