Skip to content

Commit 60c29a5

Browse files
authored
Merge pull request #2061 from orthros/pseudo-refs
feat: refs support pseudo refs
2 parents c7af04d + 43f92b5 commit 60c29a5

File tree

9 files changed

+120
-27
lines changed

9 files changed

+120
-27
lines changed

gix-features/src/fs.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,17 @@ pub mod walkdir {
207207
/// Instantiate a new directory iterator which will not skip hidden files and is sorted, with the given level of `parallelism`.
208208
///
209209
/// Use `precompose_unicode` to represent the `core.precomposeUnicode` configuration option.
210-
pub fn walkdir_sorted_new(root: &Path, _: Parallelism, precompose_unicode: bool) -> WalkDir {
210+
/// Use `max_depth` to limit the depth of the recursive walk.
211+
/// * `0`
212+
/// - Returns only the root path with no children
213+
/// * `1`
214+
/// - Root directory and children.
215+
/// * `1..n`
216+
/// - Root directory, children and {n}-grandchildren
217+
pub fn walkdir_sorted_new(root: &Path, _: Parallelism, max_depth: usize, precompose_unicode: bool) -> WalkDir {
211218
WalkDir {
212219
inner: WalkDirImpl::new(root)
220+
.max_depth(max_depth)
213221
.sort_by(|a, b| {
214222
let storage_a;
215223
let storage_b;

gix-ref/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ pub enum Category<'a> {
166166
RemoteBranch,
167167
/// A tag in `refs/notes`
168168
Note,
169-
/// Something outside of `ref/` in the current worktree, typically `HEAD`.
169+
/// Something outside `ref/` in the current worktree, typically `HEAD`.
170170
PseudoRef,
171171
/// A `PseudoRef`, but referenced so that it will always refer to the main worktree by
172172
/// prefixing it with `main-worktree/`.

gix-ref/src/store/file/loose/iter.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,30 @@ pub(in crate::store_impl::file) struct SortedLoosePaths {
1111
pub(crate) base: PathBuf,
1212
/// An prefix like `refs/heads/foo/` or `refs/heads/prefix` that a returned reference must match against..
1313
prefix: Option<BString>,
14+
/// A suffix like `HEAD` that a returned reference must match against..
15+
suffix: Option<BString>,
1416
file_walk: Option<DirEntryIter>,
1517
}
1618

1719
impl SortedLoosePaths {
18-
pub fn at(path: &Path, base: PathBuf, prefix: Option<BString>, precompose_unicode: bool) -> Self {
20+
pub fn at(
21+
path: &Path,
22+
base: PathBuf,
23+
prefix: Option<BString>,
24+
suffix: Option<BString>,
25+
precompose_unicode: bool,
26+
) -> Self {
27+
let depth = if suffix.is_some() { 1 } else { usize::MAX };
1928
SortedLoosePaths {
2029
base,
2130
prefix,
31+
suffix,
2232
file_walk: path.is_dir().then(|| {
2333
// serial iteration as we expect most refs in packed-refs anyway.
2434
gix_features::fs::walkdir_sorted_new(
2535
path,
2636
gix_features::fs::walkdir::Parallelism::Serial,
37+
depth,
2738
precompose_unicode,
2839
)
2940
.into_iter()
@@ -56,6 +67,11 @@ impl Iterator for SortedLoosePaths {
5667
continue;
5768
}
5869
}
70+
if let Some(suffix) = &self.suffix {
71+
if !full_name.ends_with(suffix) {
72+
continue;
73+
}
74+
}
5975
if gix_validate::reference::name_partial(full_name.as_bstr()).is_ok() {
6076
let name = FullName(full_name);
6177
return Some(Ok((full_path, name)));

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

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use gix_object::bstr::ByteSlice;
2+
use gix_path::RelativePath;
13
use std::{
24
borrow::Cow,
35
cmp::Ordering,
@@ -6,11 +8,8 @@ use std::{
68
path::{Path, PathBuf},
79
};
810

9-
use gix_object::bstr::ByteSlice;
10-
use gix_path::RelativePath;
11-
1211
use crate::{
13-
file::{loose, loose::iter::SortedLoosePaths},
12+
file::loose::{self, iter::SortedLoosePaths},
1413
store_impl::{file, packed},
1514
BStr, FullName, Namespace, Reference,
1615
};
@@ -85,25 +84,25 @@ impl<'p> LooseThenPacked<'p, '_> {
8584
}
8685

8786
fn convert_loose(&mut self, res: std::io::Result<(PathBuf, FullName)>) -> Result<Reference, Error> {
87+
let buf = &mut self.buf;
88+
let git_dir = self.git_dir;
89+
let common_dir = self.common_dir;
8890
let (refpath, name) = res.map_err(Error::Traversal)?;
8991
std::fs::File::open(&refpath)
9092
.and_then(|mut f| {
91-
self.buf.clear();
92-
f.read_to_end(&mut self.buf)
93+
buf.clear();
94+
f.read_to_end(buf)
9395
})
9496
.map_err(|err| Error::ReadFileContents {
9597
source: err,
9698
path: refpath.to_owned(),
9799
})?;
98-
loose::Reference::try_from_path(name, &self.buf)
100+
loose::Reference::try_from_path(name, buf)
99101
.map_err(|err| {
100102
let relative_path = refpath
101-
.strip_prefix(self.git_dir)
103+
.strip_prefix(git_dir)
102104
.ok()
103-
.or_else(|| {
104-
self.common_dir
105-
.and_then(|common_dir| refpath.strip_prefix(common_dir).ok())
106-
})
105+
.or_else(|| common_dir.and_then(|common_dir| refpath.strip_prefix(common_dir).ok()))
107106
.expect("one of our bases contains the path");
108107
Error::ReferenceCreation {
109108
source: err,
@@ -191,9 +190,9 @@ impl Iterator for LooseThenPacked<'_, '_> {
191190
}
192191

193192
impl Platform<'_> {
194-
/// Return an iterator over all references, loose or `packed`, sorted by their name.
193+
/// Return an iterator over all references, loose or packed, sorted by their name.
195194
///
196-
/// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves.
195+
/// Errors are returned similarly to what would happen when loose and packed refs were iterated by themselves.
197196
pub fn all(&self) -> std::io::Result<LooseThenPacked<'_, '_>> {
198197
self.store.iter_packed(self.packed.as_ref().map(|b| &***b))
199198
}
@@ -210,12 +209,18 @@ impl Platform<'_> {
210209
self.store
211210
.iter_prefixed_packed(prefix, self.packed.as_ref().map(|b| &***b))
212211
}
212+
213+
/// Return an iterator over the pseudo references, like `HEAD` or `FETCH_HEAD`, or anything else suffixed with `HEAD`
214+
/// in the root of the `.git` directory, sorted by name.
215+
pub fn pseudo(&self) -> std::io::Result<LooseThenPacked<'_, '_>> {
216+
self.store.iter_pseudo()
217+
}
213218
}
214219

215220
impl file::Store {
216221
/// Return a platform to obtain iterator over all references, or prefixed ones, loose or packed, sorted by their name.
217222
///
218-
/// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves.
223+
/// Errors are returned similarly to what would happen when loose and packed refs were iterated by themselves.
219224
///
220225
/// Note that since packed-refs are storing refs as precomposed unicode if [`Self::precompose_unicode`] is true, for consistency
221226
/// we also return loose references as precomposed unicode.
@@ -254,6 +259,10 @@ pub(crate) enum IterInfo<'a> {
254259
/// If `true`, we will convert decomposed into precomposed unicode.
255260
precompose_unicode: bool,
256261
},
262+
Pseudo {
263+
base: &'a Path,
264+
precompose_unicode: bool,
265+
},
257266
}
258267

259268
impl<'a> IterInfo<'a> {
@@ -263,6 +272,7 @@ impl<'a> IterInfo<'a> {
263272
IterInfo::PrefixAndBase { prefix, .. } => Some(gix_path::into_bstr(*prefix)),
264273
IterInfo::BaseAndIterRoot { prefix, .. } => Some(gix_path::into_bstr(prefix.clone())),
265274
IterInfo::ComputedIterationRoot { prefix, .. } => Some(prefix.clone()),
275+
IterInfo::Pseudo { .. } => None,
266276
}
267277
}
268278

@@ -271,24 +281,34 @@ impl<'a> IterInfo<'a> {
271281
IterInfo::Base {
272282
base,
273283
precompose_unicode,
274-
} => SortedLoosePaths::at(&base.join("refs"), base.into(), None, precompose_unicode),
284+
} => SortedLoosePaths::at(&base.join("refs"), base.into(), None, None, precompose_unicode),
275285
IterInfo::BaseAndIterRoot {
276286
base,
277287
iter_root,
278288
prefix: _,
279289
precompose_unicode,
280-
} => SortedLoosePaths::at(&iter_root, base.into(), None, precompose_unicode),
290+
} => SortedLoosePaths::at(&iter_root, base.into(), None, None, precompose_unicode),
281291
IterInfo::PrefixAndBase {
282292
base,
283293
prefix,
284294
precompose_unicode,
285-
} => SortedLoosePaths::at(&base.join(prefix), base.into(), None, precompose_unicode),
295+
} => SortedLoosePaths::at(&base.join(prefix), base.into(), None, None, precompose_unicode),
286296
IterInfo::ComputedIterationRoot {
287297
iter_root,
288298
base,
289299
prefix,
290300
precompose_unicode,
291-
} => SortedLoosePaths::at(&iter_root, base.into(), Some(prefix.into_owned()), precompose_unicode),
301+
} => SortedLoosePaths::at(
302+
&iter_root,
303+
base.into(),
304+
Some(prefix.into_owned()),
305+
None,
306+
precompose_unicode,
307+
),
308+
IterInfo::Pseudo {
309+
base,
310+
precompose_unicode,
311+
} => SortedLoosePaths::at(base, base.into(), None, Some("HEAD".into()), precompose_unicode),
292312
}
293313
.peekable()
294314
}
@@ -321,7 +341,7 @@ impl<'a> IterInfo<'a> {
321341
impl file::Store {
322342
/// Return an iterator over all references, loose or `packed`, sorted by their name.
323343
///
324-
/// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves.
344+
/// Errors are returned similarly to what would happen when loose and packed refs were iterated by themselves.
325345
pub fn iter_packed<'s, 'p>(
326346
&'s self,
327347
packed: Option<&'p packed::Buffer>,
@@ -354,6 +374,21 @@ impl file::Store {
354374
}
355375
}
356376

377+
/// Return an iterator over the pseudo references, like `HEAD` or `FETCH_HEAD`, or anything else suffixed with `HEAD`
378+
/// in the root of the `.git` directory, sorted by name.
379+
///
380+
/// Errors are returned similarly to what would happen when loose refs were iterated by themselves.
381+
pub fn iter_pseudo<'p>(&'_ self) -> std::io::Result<LooseThenPacked<'p, '_>> {
382+
self.iter_from_info(
383+
IterInfo::Pseudo {
384+
base: self.git_dir(),
385+
precompose_unicode: self.precompose_unicode,
386+
},
387+
None,
388+
None,
389+
)
390+
}
391+
357392
/// As [`iter(…)`](file::Store::iter()), but filters by `prefix`, i.e. `refs/heads/` or
358393
/// `refs/heads/feature-`.
359394
/// Note that if a prefix isn't using a trailing `/`, like in `refs/heads/foo`, it will effectively
Binary file not shown.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
git commit -m "init" --allow-empty
6+
7+
git rev-parse HEAD > .git/JIRI_HEAD
8+
touch .git/SOME_ALL_CAPS_FILE
9+
touch .git/refs/SHOULD_BE_EXCLUDED_HEAD
10+
11+
cat <<EOF >> .git/FETCH_HEAD
12+
9064ea31fae4dc59a56bdd3a06c0ddc990ee689e branch 'main' of https://github.com/Byron/gitoxide
13+
1b8d9e6a408e480ae1912e919c37a26e5c46639d not-for-merge branch 'faster-discovery' of https://github.com/Byron/gitoxide
14+
EOF

gix-ref/tests/refs/file/store/iter.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
use gix_object::bstr::ByteSlice;
2-
31
use crate::{
42
file::{store, store_at, store_with_packed_refs},
53
hex_to_id,
64
};
5+
use gix_object::bstr::ByteSlice;
76

87
mod with_namespace {
98
use gix_object::bstr::{BString, ByteSlice};
@@ -257,6 +256,20 @@ fn packed_file_iter() -> crate::Result {
257256
Ok(())
258257
}
259258

259+
#[test]
260+
fn pseudo_refs_iter() -> crate::Result {
261+
let store = store_at("make_pseudo_ref_repository.sh")?;
262+
263+
let actual = store
264+
.iter_pseudo()?
265+
.map(Result::unwrap)
266+
.map(|r: gix_ref::Reference| r.name.as_bstr().to_string())
267+
.collect::<Vec<_>>();
268+
269+
assert_eq!(actual, ["FETCH_HEAD", "HEAD", "JIRI_HEAD"]);
270+
Ok(())
271+
}
272+
260273
#[test]
261274
fn loose_iter_with_broken_refs() -> crate::Result {
262275
let store = store()?;

gix-submodule/tests/file/baseline.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fn common_values_and_names_by_path() -> crate::Result {
6161

6262
fn module_files() -> impl Iterator<Item = (PathBuf, PathBuf)> {
6363
let dir = gix_testtools::scripted_fixture_read_only("basic.sh").expect("valid fixture");
64-
gix_features::fs::walkdir_sorted_new(&dir, Parallelism::Serial, false)
64+
gix_features::fs::walkdir_sorted_new(&dir, Parallelism::Serial, usize::MAX, false)
6565
.follow_links(false)
6666
.into_iter()
6767
.filter_map(move |entry| {

gix/src/reference/iter.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ impl<'r> Iter<'r> {
3232
}
3333

3434
impl Platform<'_> {
35-
/// Return an iterator over all references in the repository.
35+
/// Return an iterator over all references in the repository, excluding
36+
/// pseudo references.
3637
///
3738
/// Even broken or otherwise unparsable or inaccessible references are returned and have to be handled by the caller on a
3839
/// case by case basis.
@@ -69,6 +70,12 @@ impl Platform<'_> {
6970
))
7071
}
7172

73+
// TODO: tests
74+
/// Return an iterator over all local pseudo references.
75+
pub fn pseudo(&self) -> Result<Iter<'_>, init::Error> {
76+
Ok(Iter::new(self.repo, self.platform.pseudo()?))
77+
}
78+
7279
// TODO: tests
7380
/// Return an iterator over all remote branches.
7481
///

0 commit comments

Comments
 (0)