Skip to content

Commit 3f914e8

Browse files
committed
Merge branch 'improvements-for-crates-index'
2 parents 57cab40 + caa8fb9 commit 3f914e8

File tree

10 files changed

+148
-28
lines changed

10 files changed

+148
-28
lines changed

cargo-smart-release/src/git/history.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,10 @@ fn add_item_if_package_changed<'a>(
259259
let mut repo = ctx.repo.clone();
260260
repo.object_cache_size(1024 * 1024);
261261
let current = gix::Tree::from_data(item.id, data_by_tree_id[&item.tree_id].to_owned(), &ctx.repo)
262-
.lookup_entry(components.iter().copied())?;
262+
.peel_to_entry(components.iter().copied())?;
263263
let parent = match item.parent_tree_id {
264264
Some(tree_id) => gix::Tree::from_data(tree_id, data_by_tree_id[&tree_id].to_owned(), &ctx.repo)
265-
.lookup_entry(components.iter().copied())?,
265+
.peel_to_entry(components.iter().copied())?,
266266
None => None,
267267
};
268268
match (current, parent) {

cargo-smart-release/src/git/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ pub fn change_since_last_release(package: &Package, ctx: &crate::Context) -> any
4646
.object()?
4747
.peel_to_kind(object::Kind::Tree)?
4848
.into_tree()
49-
.lookup_entry(components.clone())?
49+
.peel_to_entry(components.clone())?
5050
.expect("path must exist in current commit")
5151
.object_id();
5252
let released_dir_id = released_target
5353
.object()?
5454
.peel_to_kind(object::Kind::Tree)?
5555
.into_tree()
56-
.lookup_entry(components)?
56+
.peel_to_entry(components)?
5757
.expect("path must exist as it was supposedly released there")
5858
.object_id();
5959

gix-object/src/tree/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl EntryMode {
6363
}
6464

6565
/// An element of a [`TreeRef`][crate::TreeRef::entries].
66-
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
66+
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)]
6767
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6868
pub struct EntryRef<'a> {
6969
/// The kind of object to which `oid` is pointing.

gix/src/ext/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
pub use object_id::ObjectIdExt;
22
pub use reference::ReferenceExt;
33
pub use rev_spec::RevSpecExt;
4-
pub use tree::TreeIterExt;
4+
pub use tree::{TreeEntryExt, TreeEntryRefExt, TreeIterExt};
55

66
mod object_id;
77
mod reference;

gix/src/ext/tree.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,27 @@ impl<'d> TreeIterExt for TreeRefIter<'d> {
4242
breadthfirst(self.clone(), state, find, delegate)
4343
}
4444
}
45+
46+
/// Extensions for [EntryRef][gix_object::tree::EntryRef].
47+
pub trait TreeEntryRefExt<'a>: 'a {
48+
/// Attach [`Repository`][crate::Repository] to the given tree entry. It can be detached later with `detach()`.
49+
fn attach<'repo>(self, repo: &'repo crate::Repository) -> crate::object::tree::EntryRef<'repo, 'a>;
50+
}
51+
52+
impl<'a> TreeEntryRefExt<'a> for gix_object::tree::EntryRef<'a> {
53+
fn attach<'repo>(self, repo: &'repo crate::Repository) -> crate::object::tree::EntryRef<'repo, 'a> {
54+
crate::object::tree::EntryRef { inner: self, repo }
55+
}
56+
}
57+
58+
/// Extensions for [Entry][gix_object::tree::Entry].
59+
pub trait TreeEntryExt {
60+
/// Attach [`Repository`][crate::Repository] to the given tree entry. It can be detached later with `detach()`.
61+
fn attach(self, repo: &crate::Repository) -> crate::object::tree::Entry<'_>;
62+
}
63+
64+
impl TreeEntryExt for gix_object::tree::Entry {
65+
fn attach(self, repo: &crate::Repository) -> crate::object::tree::Entry<'_> {
66+
crate::object::tree::Entry { inner: self, repo }
67+
}
68+
}

gix/src/object/tree/iter.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,25 @@ impl<'repo, 'a> EntryRef<'repo, 'a> {
2525
crate::Id::from_id(self.inner.oid, self.repo)
2626
}
2727

28-
/// Return the entries id, without repository connection.
29-
pub fn oid(&self) -> gix_hash::ObjectId {
28+
/// Return the plain object id of this entry, without access to the repository.
29+
pub fn oid(&self) -> &gix_hash::oid {
30+
self.inner.oid
31+
}
32+
33+
/// Return the object this entry points to.
34+
pub fn object(&self) -> Result<crate::Object<'repo>, crate::object::find::existing::Error> {
35+
self.id().object()
36+
}
37+
38+
/// Return the plain object id of this entry, without access to the repository.
39+
pub fn object_id(&self) -> gix_hash::ObjectId {
3040
self.inner.oid.to_owned()
3141
}
42+
43+
/// Detach the repository from this instance.
44+
pub fn detach(&self) -> gix_object::tree::EntryRef<'a> {
45+
self.inner
46+
}
3247
}
3348

3449
impl<'repo, 'a> std::fmt::Display for EntryRef<'repo, 'a> {

gix/src/object/tree/mod.rs

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use gix_hash::ObjectId;
22
pub use gix_object::tree::EntryMode;
33
use gix_object::{bstr::BStr, TreeRefIter};
4+
use gix_odb::FindExt;
45

56
use crate::{object::find, Id, Tree};
67

@@ -28,22 +29,70 @@ impl<'repo> Tree<'repo> {
2829
gix_object::TreeRef::from_bytes(&self.data)
2930
}
3031

32+
/// Find the entry named `name` by iteration, or return `None` if it wasn't found.
33+
pub fn find_entry(&self, name: impl PartialEq<BStr>) -> Option<EntryRef<'repo, '_>> {
34+
TreeRefIter::from_bytes(&self.data)
35+
.filter_map(Result::ok)
36+
.find(|entry| name.eq(entry.filename))
37+
.map(|entry| EntryRef {
38+
inner: entry,
39+
repo: self.repo,
40+
})
41+
}
42+
3143
// TODO: tests.
3244
/// Follow a sequence of `path` components starting from this instance, and look them up one by one until the last component
3345
/// is looked up and its tree entry is returned.
46+
/// Use `buf` as temporary location for sub-trees to avoid allocating a temporary buffer for each lookup.
3447
///
3548
/// # Performance Notes
3649
///
3750
/// Searching tree entries is currently done in sequence, which allows to the search to be allocation free. It would be possible
3851
/// to re-use a vector and use a binary search instead, which might be able to improve performance over all.
3952
/// However, a benchmark should be created first to have some data and see which trade-off to choose here.
4053
///
41-
/// # Why is this consuming?
54+
pub fn lookup_entry<I, P>(&self, path: I, buf: &mut Vec<u8>) -> Result<Option<Entry<'repo>>, find::existing::Error>
55+
where
56+
I: IntoIterator<Item = P>,
57+
P: PartialEq<BStr>,
58+
{
59+
let mut path = path.into_iter().peekable();
60+
while let Some(component) = path.next() {
61+
match TreeRefIter::from_bytes(&self.data)
62+
.filter_map(Result::ok)
63+
.find(|entry| component.eq(entry.filename))
64+
{
65+
Some(entry) => {
66+
if path.peek().is_none() {
67+
return Ok(Some(Entry {
68+
inner: entry.into(),
69+
repo: self.repo,
70+
}));
71+
} else {
72+
let obj = self.repo.objects.find(entry.oid, buf)?;
73+
if !obj.kind.is_tree() {
74+
return Ok(None);
75+
}
76+
}
77+
}
78+
None => return Ok(None),
79+
}
80+
}
81+
Ok(None)
82+
}
83+
84+
/// Follow a sequence of `path` components starting from this instance, and look them up one by one until the last component
85+
/// is looked up and its tree entry is returned, while changing this instance to point to the last seen tree.
86+
/// Note that if the lookup fails, it may be impossible to continue making lookups through this tree.
87+
/// It's useful to have this function to be able to reuse the internal buffer of the tree.
88+
///
89+
/// # Performance Notes
90+
///
91+
/// Searching tree entries is currently done in sequence, which allows to the search to be allocation free. It would be possible
92+
/// to re-use a vector and use a binary search instead, which might be able to improve performance over all.
93+
/// However, a benchmark should be created first to have some data and see which trade-off to choose here.
4294
///
43-
/// The borrow checker shows pathological behaviour in loops that mutate a buffer, but also want to return from it.
44-
/// Workarounds include keeping an index and doing a separate access to the memory, which seems hard to do here without
45-
/// re-parsing the entries.
46-
pub fn lookup_entry<I, P>(mut self, path: I) -> Result<Option<Entry<'repo>>, find::existing::Error>
95+
pub fn peel_to_entry<I, P>(&mut self, path: I) -> Result<Option<Entry<'repo>>, find::existing::Error>
4796
where
4897
I: IntoIterator<Item = P>,
4998
P: PartialEq<BStr>,
@@ -62,12 +111,11 @@ impl<'repo> Tree<'repo> {
62111
}));
63112
} else {
64113
let next_id = entry.oid.to_owned();
65-
let repo = self.repo;
66-
drop(self);
67-
self = match repo.find_object(next_id)?.try_into_tree() {
68-
Ok(tree) => tree,
69-
Err(_) => return Ok(None),
70-
};
114+
let obj = self.repo.objects.find(next_id, &mut self.data)?;
115+
self.id = next_id;
116+
if !obj.kind.is_tree() {
117+
return Ok(None);
118+
}
71119
}
72120
}
73121
None => return Ok(None),
@@ -76,18 +124,40 @@ impl<'repo> Tree<'repo> {
76124
Ok(None)
77125
}
78126

79-
/// Like [`lookup_entry()`][Self::lookup_entry()], but takes a `Path` directly via `relative_path`, a path relative to this tree.
127+
/// Like [`Self::lookup_entry()`], but takes a `Path` directly via `relative_path`, a path relative to this tree.
80128
///
81129
/// # Note
82130
///
83131
/// If any path component contains illformed UTF-8 and thus can't be converted to bytes on platforms which can't do so natively,
84132
/// the returned component will be empty which makes the lookup fail.
85133
pub fn lookup_entry_by_path(
86-
self,
134+
&self,
135+
relative_path: impl AsRef<std::path::Path>,
136+
buf: &mut Vec<u8>,
137+
) -> Result<Option<Entry<'repo>>, find::existing::Error> {
138+
use crate::bstr::ByteSlice;
139+
self.lookup_entry(
140+
relative_path.as_ref().components().map(|c: std::path::Component<'_>| {
141+
gix_path::os_str_into_bstr(c.as_os_str())
142+
.unwrap_or_else(|_| "".into())
143+
.as_bytes()
144+
}),
145+
buf,
146+
)
147+
}
148+
149+
/// Like [`Self::peel_to_entry()`], but takes a `Path` directly via `relative_path`, a path relative to this tree.
150+
///
151+
/// # Note
152+
///
153+
/// If any path component contains illformed UTF-8 and thus can't be converted to bytes on platforms which can't do so natively,
154+
/// the returned component will be empty which makes the lookup fail.
155+
pub fn peel_to_entry_by_path(
156+
&mut self,
87157
relative_path: impl AsRef<std::path::Path>,
88158
) -> Result<Option<Entry<'repo>>, find::existing::Error> {
89159
use crate::bstr::ByteSlice;
90-
self.lookup_entry(relative_path.as_ref().components().map(|c: std::path::Component<'_>| {
160+
self.peel_to_entry(relative_path.as_ref().components().map(|c: std::path::Component<'_>| {
91161
gix_path::os_str_into_bstr(c.as_os_str())
92162
.unwrap_or_else(|_| "".into())
93163
.as_bytes()
@@ -114,8 +184,8 @@ impl<'r> std::fmt::Debug for Tree<'r> {
114184
/// An entry in a [`Tree`], similar to an entry in a directory.
115185
#[derive(PartialEq, Debug, Clone)]
116186
pub struct Entry<'repo> {
117-
inner: gix_object::tree::Entry,
118-
repo: &'repo crate::Repository,
187+
pub(crate) inner: gix_object::tree::Entry,
188+
pub(crate) repo: &'repo crate::Repository,
119189
}
120190

121191
mod entry {

gix/src/repository/snapshots.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,11 @@ impl crate::Repository {
3737
});
3838
match self.work_dir() {
3939
None => {
40-
// TODO: replace with ref-spec `HEAD:.mailmap` for less verbose way of getting the blob id
4140
blob_id = blob_id.or_else(|| {
4241
self.head().ok().and_then(|mut head| {
4342
let commit = head.peel_to_commit_in_place().ok()?;
4443
let tree = commit.tree().ok()?;
45-
tree.lookup_entry(Some(".mailmap")).ok()?.map(|e| e.object_id())
44+
tree.find_entry(".mailmap").map(|e| e.object_id())
4645
})
4746
});
4847
}

gix/src/revision/spec/parse/delegate/navigate.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
123123
if path.is_empty() {
124124
return Ok(tree_id);
125125
}
126-
let tree = repo.find_object(tree_id)?.into_tree();
126+
let mut tree = repo.find_object(tree_id)?.into_tree();
127127
let entry =
128-
tree.lookup_entry_by_path(gix_path::from_bstr(path))?
128+
tree.peel_to_entry_by_path(gix_path::from_bstr(path))?
129129
.ok_or_else(|| Error::PathNotFound {
130130
path: path.into(),
131131
object: obj.attach(repo).shorten_or_id(),

gix/tests/object/tree/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,13 @@
1+
use crate::util::named_repo;
2+
13
mod diff;
4+
5+
#[test]
6+
fn find_entry() -> crate::Result {
7+
let repo = named_repo("make_basic_repo.sh")?;
8+
let tree = repo.head_commit()?.tree()?;
9+
assert_eq!(tree.find_entry("this").expect("present").filename(), "this");
10+
11+
assert!(tree.find_entry("not there").is_none());
12+
Ok(())
13+
}

0 commit comments

Comments
 (0)