diff --git a/gix-features/src/fs.rs b/gix-features/src/fs.rs index 1838dcdbd87..164d6913483 100644 --- a/gix-features/src/fs.rs +++ b/gix-features/src/fs.rs @@ -207,9 +207,17 @@ pub mod walkdir { /// Instantiate a new directory iterator which will not skip hidden files and is sorted, with the given level of `parallelism`. /// /// Use `precompose_unicode` to represent the `core.precomposeUnicode` configuration option. - pub fn walkdir_sorted_new(root: &Path, _: Parallelism, precompose_unicode: bool) -> WalkDir { + /// Use `max_depth` to limit the depth of the recursive walk. + /// * `0` + /// - Returns only the root path with no children + /// * `1` + /// - Root directory and children. + /// * `1..n` + /// - Root directory, children and {n}-grandchildren + pub fn walkdir_sorted_new(root: &Path, _: Parallelism, max_depth: usize, precompose_unicode: bool) -> WalkDir { WalkDir { inner: WalkDirImpl::new(root) + .max_depth(max_depth) .sort_by(|a, b| { let storage_a; let storage_b; diff --git a/gix-ref/src/lib.rs b/gix-ref/src/lib.rs index a564d0f6488..1d9eded44cc 100644 --- a/gix-ref/src/lib.rs +++ b/gix-ref/src/lib.rs @@ -166,7 +166,7 @@ pub enum Category<'a> { RemoteBranch, /// A tag in `refs/notes` Note, - /// Something outside of `ref/` in the current worktree, typically `HEAD`. + /// Something outside `ref/` in the current worktree, typically `HEAD`. PseudoRef, /// A `PseudoRef`, but referenced so that it will always refer to the main worktree by /// prefixing it with `main-worktree/`. diff --git a/gix-ref/src/store/file/loose/iter.rs b/gix-ref/src/store/file/loose/iter.rs index 31d91a4b094..c0c7b7f3a8b 100644 --- a/gix-ref/src/store/file/loose/iter.rs +++ b/gix-ref/src/store/file/loose/iter.rs @@ -11,19 +11,30 @@ pub(in crate::store_impl::file) struct SortedLoosePaths { pub(crate) base: PathBuf, /// An prefix like `refs/heads/foo/` or `refs/heads/prefix` that a returned reference must match against.. prefix: Option, + /// A suffix like `HEAD` that a returned reference must match against.. + suffix: Option, file_walk: Option, } impl SortedLoosePaths { - pub fn at(path: &Path, base: PathBuf, prefix: Option, precompose_unicode: bool) -> Self { + pub fn at( + path: &Path, + base: PathBuf, + prefix: Option, + suffix: Option, + precompose_unicode: bool, + ) -> Self { + let depth = if suffix.is_some() { 1 } else { usize::MAX }; SortedLoosePaths { base, prefix, + suffix, file_walk: path.is_dir().then(|| { // serial iteration as we expect most refs in packed-refs anyway. gix_features::fs::walkdir_sorted_new( path, gix_features::fs::walkdir::Parallelism::Serial, + depth, precompose_unicode, ) .into_iter() @@ -56,6 +67,11 @@ impl Iterator for SortedLoosePaths { continue; } } + if let Some(suffix) = &self.suffix { + if !full_name.ends_with(suffix) { + continue; + } + } if gix_validate::reference::name_partial(full_name.as_bstr()).is_ok() { let name = FullName(full_name); return Some(Ok((full_path, name))); diff --git a/gix-ref/src/store/file/overlay_iter.rs b/gix-ref/src/store/file/overlay_iter.rs index d6c3b5ae8c8..8c4b2c36809 100644 --- a/gix-ref/src/store/file/overlay_iter.rs +++ b/gix-ref/src/store/file/overlay_iter.rs @@ -1,3 +1,5 @@ +use gix_object::bstr::ByteSlice; +use gix_path::RelativePath; use std::{ borrow::Cow, cmp::Ordering, @@ -6,11 +8,8 @@ use std::{ path::{Path, PathBuf}, }; -use gix_object::bstr::ByteSlice; -use gix_path::RelativePath; - use crate::{ - file::{loose, loose::iter::SortedLoosePaths}, + file::loose::{self, iter::SortedLoosePaths}, store_impl::{file, packed}, BStr, FullName, Namespace, Reference, }; @@ -85,25 +84,25 @@ impl<'p> LooseThenPacked<'p, '_> { } fn convert_loose(&mut self, res: std::io::Result<(PathBuf, FullName)>) -> Result { + let buf = &mut self.buf; + let git_dir = self.git_dir; + let common_dir = self.common_dir; let (refpath, name) = res.map_err(Error::Traversal)?; std::fs::File::open(&refpath) .and_then(|mut f| { - self.buf.clear(); - f.read_to_end(&mut self.buf) + buf.clear(); + f.read_to_end(buf) }) .map_err(|err| Error::ReadFileContents { source: err, path: refpath.to_owned(), })?; - loose::Reference::try_from_path(name, &self.buf) + loose::Reference::try_from_path(name, buf) .map_err(|err| { let relative_path = refpath - .strip_prefix(self.git_dir) + .strip_prefix(git_dir) .ok() - .or_else(|| { - self.common_dir - .and_then(|common_dir| refpath.strip_prefix(common_dir).ok()) - }) + .or_else(|| common_dir.and_then(|common_dir| refpath.strip_prefix(common_dir).ok())) .expect("one of our bases contains the path"); Error::ReferenceCreation { source: err, @@ -191,9 +190,9 @@ impl Iterator for LooseThenPacked<'_, '_> { } impl Platform<'_> { - /// Return an iterator over all references, loose or `packed`, sorted by their name. + /// Return an iterator over all references, loose or packed, sorted by their name. /// - /// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves. + /// Errors are returned similarly to what would happen when loose and packed refs were iterated by themselves. pub fn all(&self) -> std::io::Result> { self.store.iter_packed(self.packed.as_ref().map(|b| &***b)) } @@ -210,12 +209,18 @@ impl Platform<'_> { self.store .iter_prefixed_packed(prefix, self.packed.as_ref().map(|b| &***b)) } + + /// Return an iterator over the pseudo references, like `HEAD` or `FETCH_HEAD`, or anything else suffixed with `HEAD` + /// in the root of the `.git` directory, sorted by name. + pub fn pseudo(&self) -> std::io::Result> { + self.store.iter_pseudo() + } } impl file::Store { /// Return a platform to obtain iterator over all references, or prefixed ones, loose or packed, sorted by their name. /// - /// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves. + /// Errors are returned similarly to what would happen when loose and packed refs were iterated by themselves. /// /// Note that since packed-refs are storing refs as precomposed unicode if [`Self::precompose_unicode`] is true, for consistency /// we also return loose references as precomposed unicode. @@ -254,6 +259,10 @@ pub(crate) enum IterInfo<'a> { /// If `true`, we will convert decomposed into precomposed unicode. precompose_unicode: bool, }, + Pseudo { + base: &'a Path, + precompose_unicode: bool, + }, } impl<'a> IterInfo<'a> { @@ -263,6 +272,7 @@ impl<'a> IterInfo<'a> { IterInfo::PrefixAndBase { prefix, .. } => Some(gix_path::into_bstr(*prefix)), IterInfo::BaseAndIterRoot { prefix, .. } => Some(gix_path::into_bstr(prefix.clone())), IterInfo::ComputedIterationRoot { prefix, .. } => Some(prefix.clone()), + IterInfo::Pseudo { .. } => None, } } @@ -271,24 +281,34 @@ impl<'a> IterInfo<'a> { IterInfo::Base { base, precompose_unicode, - } => SortedLoosePaths::at(&base.join("refs"), base.into(), None, precompose_unicode), + } => SortedLoosePaths::at(&base.join("refs"), base.into(), None, None, precompose_unicode), IterInfo::BaseAndIterRoot { base, iter_root, prefix: _, precompose_unicode, - } => SortedLoosePaths::at(&iter_root, base.into(), None, precompose_unicode), + } => SortedLoosePaths::at(&iter_root, base.into(), None, None, precompose_unicode), IterInfo::PrefixAndBase { base, prefix, precompose_unicode, - } => SortedLoosePaths::at(&base.join(prefix), base.into(), None, precompose_unicode), + } => SortedLoosePaths::at(&base.join(prefix), base.into(), None, None, precompose_unicode), IterInfo::ComputedIterationRoot { iter_root, base, prefix, precompose_unicode, - } => SortedLoosePaths::at(&iter_root, base.into(), Some(prefix.into_owned()), precompose_unicode), + } => SortedLoosePaths::at( + &iter_root, + base.into(), + Some(prefix.into_owned()), + None, + precompose_unicode, + ), + IterInfo::Pseudo { + base, + precompose_unicode, + } => SortedLoosePaths::at(base, base.into(), None, Some("HEAD".into()), precompose_unicode), } .peekable() } @@ -321,7 +341,7 @@ impl<'a> IterInfo<'a> { impl file::Store { /// Return an iterator over all references, loose or `packed`, sorted by their name. /// - /// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves. + /// Errors are returned similarly to what would happen when loose and packed refs were iterated by themselves. pub fn iter_packed<'s, 'p>( &'s self, packed: Option<&'p packed::Buffer>, @@ -354,6 +374,21 @@ impl file::Store { } } + /// Return an iterator over the pseudo references, like `HEAD` or `FETCH_HEAD`, or anything else suffixed with `HEAD` + /// in the root of the `.git` directory, sorted by name. + /// + /// Errors are returned similarly to what would happen when loose refs were iterated by themselves. + pub fn iter_pseudo<'p>(&'_ self) -> std::io::Result> { + self.iter_from_info( + IterInfo::Pseudo { + base: self.git_dir(), + precompose_unicode: self.precompose_unicode, + }, + None, + None, + ) + } + /// As [`iter(…)`](file::Store::iter()), but filters by `prefix`, i.e. `refs/heads/` or /// `refs/heads/feature-`. /// Note that if a prefix isn't using a trailing `/`, like in `refs/heads/foo`, it will effectively diff --git a/gix-ref/tests/fixtures/generated-archives/make_pseudo_ref_repository.tar b/gix-ref/tests/fixtures/generated-archives/make_pseudo_ref_repository.tar new file mode 100644 index 00000000000..68efa91bb9c Binary files /dev/null and b/gix-ref/tests/fixtures/generated-archives/make_pseudo_ref_repository.tar differ diff --git a/gix-ref/tests/fixtures/make_pseudo_ref_repository.sh b/gix-ref/tests/fixtures/make_pseudo_ref_repository.sh new file mode 100755 index 00000000000..ef4b3476dae --- /dev/null +++ b/gix-ref/tests/fixtures/make_pseudo_ref_repository.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -eu -o pipefail + +git init -q +git commit -m "init" --allow-empty + +git rev-parse HEAD > .git/JIRI_HEAD +touch .git/SOME_ALL_CAPS_FILE +touch .git/refs/SHOULD_BE_EXCLUDED_HEAD + +cat <> .git/FETCH_HEAD +9064ea31fae4dc59a56bdd3a06c0ddc990ee689e branch 'main' of https://github.com/Byron/gitoxide +1b8d9e6a408e480ae1912e919c37a26e5c46639d not-for-merge branch 'faster-discovery' of https://github.com/Byron/gitoxide +EOF \ No newline at end of file diff --git a/gix-ref/tests/refs/file/store/iter.rs b/gix-ref/tests/refs/file/store/iter.rs index 68259c0b1b9..91639672e6e 100644 --- a/gix-ref/tests/refs/file/store/iter.rs +++ b/gix-ref/tests/refs/file/store/iter.rs @@ -1,9 +1,8 @@ -use gix_object::bstr::ByteSlice; - use crate::{ file::{store, store_at, store_with_packed_refs}, hex_to_id, }; +use gix_object::bstr::ByteSlice; mod with_namespace { use gix_object::bstr::{BString, ByteSlice}; @@ -257,6 +256,20 @@ fn packed_file_iter() -> crate::Result { Ok(()) } +#[test] +fn pseudo_refs_iter() -> crate::Result { + let store = store_at("make_pseudo_ref_repository.sh")?; + + let actual = store + .iter_pseudo()? + .map(Result::unwrap) + .map(|r: gix_ref::Reference| r.name.as_bstr().to_string()) + .collect::>(); + + assert_eq!(actual, ["FETCH_HEAD", "HEAD", "JIRI_HEAD"]); + Ok(()) +} + #[test] fn loose_iter_with_broken_refs() -> crate::Result { let store = store()?; diff --git a/gix-submodule/tests/file/baseline.rs b/gix-submodule/tests/file/baseline.rs index 6971cdb33a7..513b5051af0 100644 --- a/gix-submodule/tests/file/baseline.rs +++ b/gix-submodule/tests/file/baseline.rs @@ -61,7 +61,7 @@ fn common_values_and_names_by_path() -> crate::Result { fn module_files() -> impl Iterator { let dir = gix_testtools::scripted_fixture_read_only("basic.sh").expect("valid fixture"); - gix_features::fs::walkdir_sorted_new(&dir, Parallelism::Serial, false) + gix_features::fs::walkdir_sorted_new(&dir, Parallelism::Serial, usize::MAX, false) .follow_links(false) .into_iter() .filter_map(move |entry| { diff --git a/gix/src/reference/iter.rs b/gix/src/reference/iter.rs index a18ecbeda91..66952577b64 100644 --- a/gix/src/reference/iter.rs +++ b/gix/src/reference/iter.rs @@ -32,7 +32,8 @@ impl<'r> Iter<'r> { } impl Platform<'_> { - /// Return an iterator over all references in the repository. + /// Return an iterator over all references in the repository, excluding + /// pseudo references. /// /// Even broken or otherwise unparsable or inaccessible references are returned and have to be handled by the caller on a /// case by case basis. @@ -69,6 +70,12 @@ impl Platform<'_> { )) } + // TODO: tests + /// Return an iterator over all local pseudo references. + pub fn pseudo(&self) -> Result, init::Error> { + Ok(Iter::new(self.repo, self.platform.pseudo()?)) + } + // TODO: tests /// Return an iterator over all remote branches. ///