Skip to content

Commit ba563b0

Browse files
committed
fix(gix): extend lifetime of iterators
Previously the iterators would return references with lifetimes that were shorter than the actual lifetimes of the `gix::Reference` themselves. This was dues to a footgun with `'_` (eliding the lifetime). When a function returns an elided lifetime, this lifetime usually is the lifetime of the `&self` parameter, but sometimes it is the lifetime of the type itself (e.g. `Iter<'_>`). I made the lifetimes explicit to ensure we were using the correct ones. Closes #2103
1 parent 202bc6d commit ba563b0

File tree

2 files changed

+35
-19
lines changed

2 files changed

+35
-19
lines changed

gix-ref/src/store/file/overlay_iter.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,11 @@ impl Iterator for LooseThenPacked<'_, '_> {
189189
}
190190
}
191191

192-
impl Platform<'_> {
192+
impl<'repo> Platform<'repo> {
193193
/// Return an iterator over all references, loose or packed, sorted by their name.
194194
///
195195
/// Errors are returned similarly to what would happen when loose and packed refs were iterated by themselves.
196-
pub fn all(&self) -> std::io::Result<LooseThenPacked<'_, '_>> {
196+
pub fn all<'p>(&'p self) -> std::io::Result<LooseThenPacked<'p, 'repo>> {
197197
self.store.iter_packed(self.packed.as_ref().map(|b| &***b))
198198
}
199199

@@ -205,14 +205,14 @@ impl Platform<'_> {
205205
/// starts with `foo`, like `refs/heads/foo` and `refs/heads/foobar`.
206206
///
207207
/// Prefixes are relative paths with slash-separated components.
208-
pub fn prefixed(&self, prefix: &RelativePath) -> std::io::Result<LooseThenPacked<'_, '_>> {
208+
pub fn prefixed<'p>(&'p self, prefix: &RelativePath) -> std::io::Result<LooseThenPacked<'p, 'repo>> {
209209
self.store
210210
.iter_prefixed_packed(prefix, self.packed.as_ref().map(|b| &***b))
211211
}
212212

213213
/// Return an iterator over the pseudo references, like `HEAD` or `FETCH_HEAD`, or anything else suffixed with `HEAD`
214214
/// in the root of the `.git` directory, sorted by name.
215-
pub fn pseudo(&self) -> std::io::Result<LooseThenPacked<'_, '_>> {
215+
pub fn pseudo<'p>(&'p self) -> std::io::Result<LooseThenPacked<'p, 'repo>> {
216216
self.store.iter_pseudo()
217217
}
218218
}

gix/src/reference/iter.rs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ pub struct Platform<'r> {
1313
}
1414

1515
/// An iterator over references, with or without filter.
16-
pub struct Iter<'r> {
17-
inner: gix_ref::file::iter::LooseThenPacked<'r, 'r>,
16+
pub struct Iter<'p, 'r> {
17+
inner: gix_ref::file::iter::LooseThenPacked<'p, 'r>,
1818
peel_with_packed: Option<gix_ref::file::packed::SharedBufferSnapshot>,
1919
peel: bool,
2020
repo: &'r crate::Repository,
2121
}
2222

23-
impl<'r> Iter<'r> {
24-
fn new(repo: &'r crate::Repository, platform: gix_ref::file::iter::LooseThenPacked<'r, 'r>) -> Self {
23+
impl<'p, 'r> Iter<'p, 'r> {
24+
fn new(repo: &'r crate::Repository, platform: gix_ref::file::iter::LooseThenPacked<'p, 'r>) -> Self {
2525
Iter {
2626
inner: platform,
2727
peel_with_packed: None,
@@ -31,39 +31,55 @@ impl<'r> Iter<'r> {
3131
}
3232
}
3333

34-
impl Platform<'_> {
34+
impl<'repo> Platform<'repo> {
3535
/// Return an iterator over all references in the repository, excluding
3636
/// pseudo references.
3737
///
3838
/// Even broken or otherwise unparsable or inaccessible references are returned and have to be handled by the caller on a
3939
/// case by case basis.
40-
pub fn all(&self) -> Result<Iter<'_>, init::Error> {
40+
pub fn all<'p>(&'p self) -> Result<Iter<'p, 'repo>, init::Error> {
4141
Ok(Iter::new(self.repo, self.platform.all()?))
4242
}
4343

4444
/// Return an iterator over all references that match the given `prefix`.
4545
///
4646
/// These are of the form `refs/heads/` or `refs/remotes/origin`, and must not contain relative paths components like `.` or `..`.
47-
pub fn prefixed<'a>(
48-
&self,
47+
pub fn prefixed<'p, 'a>(
48+
&'p self,
4949
prefix: impl TryInto<&'a RelativePath, Error = gix_path::relative_path::Error>,
50-
) -> Result<Iter<'_>, init::Error> {
50+
) -> Result<Iter<'p, 'repo>, init::Error> {
5151
Ok(Iter::new(self.repo, self.platform.prefixed(prefix.try_into()?)?))
5252
}
5353

5454
// TODO: tests
5555
/// Return an iterator over all references that are tags.
5656
///
5757
/// They are all prefixed with `refs/tags`.
58-
pub fn tags(&self) -> Result<Iter<'_>, init::Error> {
58+
///
59+
/// ```rust
60+
/// # // Regression test for https://github.com/GitoxideLabs/gitoxide/issues/2103
61+
/// # // This only ensures we can return a reference, not that the code below is correct
62+
/// /// Get the latest tag that isn't a pre-release version
63+
/// fn latest_stable_tag(repo: &gix::Repository) -> Result<gix::Reference<'_>, Box<dyn std::error::Error>> {
64+
/// repo.references()?
65+
/// .tags()?
66+
/// .filter_map(|tag| tag.ok())
67+
/// // Warning: lexically sorting version numbers is incorrect, use the semver crate if
68+
/// // you want correct results
69+
/// .max_by_key(|tag| tag.name().shorten().to_owned())
70+
/// .ok_or(std::io::Error::other("latest tag not found"))
71+
/// .map_err(Into::into)
72+
/// }
73+
/// ```
74+
pub fn tags<'p>(&'p self) -> Result<Iter<'p, 'repo>, init::Error> {
5975
Ok(Iter::new(self.repo, self.platform.prefixed(b"refs/tags/".try_into()?)?))
6076
}
6177

6278
// TODO: tests
6379
/// Return an iterator over all local branches.
6480
///
6581
/// They are all prefixed with `refs/heads`.
66-
pub fn local_branches(&self) -> Result<Iter<'_>, init::Error> {
82+
pub fn local_branches<'p>(&'p self) -> Result<Iter<'p, 'repo>, init::Error> {
6783
Ok(Iter::new(
6884
self.repo,
6985
self.platform.prefixed(b"refs/heads/".try_into()?)?,
@@ -72,23 +88,23 @@ impl Platform<'_> {
7288

7389
// TODO: tests
7490
/// Return an iterator over all local pseudo references.
75-
pub fn pseudo(&self) -> Result<Iter<'_>, init::Error> {
91+
pub fn pseudo<'p>(&'p self) -> Result<Iter<'p, 'repo>, init::Error> {
7692
Ok(Iter::new(self.repo, self.platform.pseudo()?))
7793
}
7894

7995
// TODO: tests
8096
/// Return an iterator over all remote branches.
8197
///
8298
/// They are all prefixed with `refs/remotes`.
83-
pub fn remote_branches(&self) -> Result<Iter<'_>, init::Error> {
99+
pub fn remote_branches<'p>(&'p self) -> Result<Iter<'p, 'repo>, init::Error> {
84100
Ok(Iter::new(
85101
self.repo,
86102
self.platform.prefixed(b"refs/remotes/".try_into()?)?,
87103
))
88104
}
89105
}
90106

91-
impl Iter<'_> {
107+
impl Iter<'_, '_> {
92108
/// Automatically peel references before yielding them during iteration.
93109
///
94110
/// This has the same effect as using `iter.map(|r| {r.peel_to_id_in_place(); r})`.
@@ -104,7 +120,7 @@ impl Iter<'_> {
104120
}
105121
}
106122

107-
impl<'r> Iterator for Iter<'r> {
123+
impl<'r> Iterator for Iter<'_, 'r> {
108124
type Item = Result<crate::Reference<'r>, Box<dyn std::error::Error + Send + Sync + 'static>>;
109125

110126
fn next(&mut self) -> Option<Self::Item> {

0 commit comments

Comments
 (0)