Skip to content

Commit fdf5153

Browse files
orthrosByron
authored andcommitted
feat: refs support pseudo refs
1 parent a2741da commit fdf5153

File tree

6 files changed

+160
-34
lines changed

6 files changed

+160
-34
lines changed

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,31 @@ 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+
root_only: bool,
26+
precompose_unicode: bool,
27+
) -> Self {
28+
let depth = if root_only { 1 } else { usize::MAX };
1929
SortedLoosePaths {
2030
base,
2131
prefix,
32+
suffix,
2233
file_walk: path.is_dir().then(|| {
2334
// serial iteration as we expect most refs in packed-refs anyway.
2435
gix_features::fs::walkdir_sorted_new(
2536
path,
2637
gix_features::fs::walkdir::Parallelism::Serial,
27-
usize::MAX,
38+
depth,
2839
precompose_unicode,
2940
)
3041
.into_iter()
@@ -57,6 +68,11 @@ impl Iterator for SortedLoosePaths {
5768
continue;
5869
}
5970
}
71+
if let Some(suffix) = &self.suffix {
72+
if !full_name.ends_with(suffix) {
73+
continue;
74+
}
75+
}
6076
if gix_validate::reference::name_partial(full_name.as_bstr()).is_ok() {
6177
let name = FullName(full_name);
6278
return Some(Ok((full_path, name)));

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

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use gix_object::bstr::ByteSlice;
1010
use gix_path::RelativePath;
1111

1212
use crate::{
13-
file::{loose, loose::iter::SortedLoosePaths},
13+
file::loose::{self, iter::SortedLoosePaths},
1414
store_impl::{file, packed},
1515
BStr, FullName, Namespace, Reference,
1616
};
@@ -85,36 +85,48 @@ impl<'p> LooseThenPacked<'p, '_> {
8585
}
8686

8787
fn convert_loose(&mut self, res: std::io::Result<(PathBuf, FullName)>) -> Result<Reference, Error> {
88-
let (refpath, name) = res.map_err(Error::Traversal)?;
89-
std::fs::File::open(&refpath)
90-
.and_then(|mut f| {
91-
self.buf.clear();
92-
f.read_to_end(&mut self.buf)
93-
})
94-
.map_err(|err| Error::ReadFileContents {
95-
source: err,
96-
path: refpath.to_owned(),
97-
})?;
98-
loose::Reference::try_from_path(name, &self.buf)
99-
.map_err(|err| {
100-
let relative_path = refpath
101-
.strip_prefix(self.git_dir)
102-
.ok()
103-
.or_else(|| {
104-
self.common_dir
105-
.and_then(|common_dir| refpath.strip_prefix(common_dir).ok())
106-
})
107-
.expect("one of our bases contains the path");
108-
Error::ReferenceCreation {
109-
source: err,
110-
relative_path: relative_path.into(),
111-
}
112-
})
113-
.map(Into::into)
114-
.map(|r| self.strip_namespace(r))
88+
convert_loose(&mut self.buf, self.git_dir, self.common_dir, self.namespace, res)
11589
}
11690
}
11791

92+
pub(crate) fn convert_loose(
93+
buf: &mut Vec<u8>,
94+
git_dir: &Path,
95+
common_dir: Option<&Path>,
96+
namespace: Option<&Namespace>,
97+
res: std::io::Result<(PathBuf, FullName)>,
98+
) -> Result<Reference, Error> {
99+
let (refpath, name) = res.map_err(Error::Traversal)?;
100+
std::fs::File::open(&refpath)
101+
.and_then(|mut f| {
102+
buf.clear();
103+
f.read_to_end(buf)
104+
})
105+
.map_err(|err| Error::ReadFileContents {
106+
source: err,
107+
path: refpath.to_owned(),
108+
})?;
109+
loose::Reference::try_from_path(name, buf)
110+
.map_err(|err| {
111+
let relative_path = refpath
112+
.strip_prefix(git_dir)
113+
.ok()
114+
.or_else(|| common_dir.and_then(|common_dir| refpath.strip_prefix(common_dir).ok()))
115+
.expect("one of our bases contains the path");
116+
Error::ReferenceCreation {
117+
source: err,
118+
relative_path: relative_path.into(),
119+
}
120+
})
121+
.map(Into::into)
122+
.map(|mut r: Reference| {
123+
if let Some(namespace) = namespace {
124+
r.strip_namespace(namespace);
125+
}
126+
r
127+
})
128+
}
129+
118130
impl Iterator for LooseThenPacked<'_, '_> {
119131
type Item = Result<Reference, Error>;
120132

@@ -210,6 +222,11 @@ impl Platform<'_> {
210222
self.store
211223
.iter_prefixed_packed(prefix, self.packed.as_ref().map(|b| &***b))
212224
}
225+
226+
/// Return an iterator over the pseudo references
227+
pub fn psuedo_refs(&self) -> std::io::Result<LooseThenPacked<'_, '_>> {
228+
self.store.iter_pseudo_refs()
229+
}
213230
}
214231

215232
impl file::Store {
@@ -254,6 +271,10 @@ pub(crate) enum IterInfo<'a> {
254271
/// If `true`, we will convert decomposed into precomposed unicode.
255272
precompose_unicode: bool,
256273
},
274+
PseudoRefs {
275+
base: &'a Path,
276+
precompose_unicode: bool,
277+
},
257278
}
258279

259280
impl<'a> IterInfo<'a> {
@@ -263,6 +284,7 @@ impl<'a> IterInfo<'a> {
263284
IterInfo::PrefixAndBase { prefix, .. } => Some(gix_path::into_bstr(*prefix)),
264285
IterInfo::BaseAndIterRoot { prefix, .. } => Some(gix_path::into_bstr(prefix.clone())),
265286
IterInfo::ComputedIterationRoot { prefix, .. } => Some(prefix.clone()),
287+
IterInfo::PseudoRefs { .. } => None,
266288
}
267289
}
268290

@@ -271,24 +293,35 @@ impl<'a> IterInfo<'a> {
271293
IterInfo::Base {
272294
base,
273295
precompose_unicode,
274-
} => SortedLoosePaths::at(&base.join("refs"), base.into(), None, precompose_unicode),
296+
} => SortedLoosePaths::at(&base.join("refs"), base.into(), None, None, false, precompose_unicode),
275297
IterInfo::BaseAndIterRoot {
276298
base,
277299
iter_root,
278300
prefix: _,
279301
precompose_unicode,
280-
} => SortedLoosePaths::at(&iter_root, base.into(), None, precompose_unicode),
302+
} => SortedLoosePaths::at(&iter_root, base.into(), None, None, false, precompose_unicode),
281303
IterInfo::PrefixAndBase {
282304
base,
283305
prefix,
284306
precompose_unicode,
285-
} => SortedLoosePaths::at(&base.join(prefix), base.into(), None, precompose_unicode),
307+
} => SortedLoosePaths::at(&base.join(prefix), base.into(), None, None, false, precompose_unicode),
286308
IterInfo::ComputedIterationRoot {
287309
iter_root,
288310
base,
289311
prefix,
290312
precompose_unicode,
291-
} => SortedLoosePaths::at(&iter_root, base.into(), Some(prefix.into_owned()), precompose_unicode),
313+
} => SortedLoosePaths::at(
314+
&iter_root,
315+
base.into(),
316+
Some(prefix.into_owned()),
317+
None,
318+
false,
319+
precompose_unicode,
320+
),
321+
IterInfo::PseudoRefs {
322+
base,
323+
precompose_unicode,
324+
} => SortedLoosePaths::at(base, base.into(), None, Some("HEAD".into()), true, precompose_unicode),
292325
}
293326
.peekable()
294327
}
@@ -354,6 +387,20 @@ impl file::Store {
354387
}
355388
}
356389

390+
/// Return an iterator over all pseudo references, loose or `packed`, sorted by their name.
391+
///
392+
/// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves.
393+
pub fn iter_pseudo_refs<'p>(&'_ self) -> std::io::Result<LooseThenPacked<'p, '_>> {
394+
self.iter_from_info(
395+
IterInfo::PseudoRefs {
396+
base: self.git_dir(),
397+
precompose_unicode: self.precompose_unicode,
398+
},
399+
None,
400+
None,
401+
)
402+
}
403+
357404
/// As [`iter(…)`](file::Store::iter()), but filters by `prefix`, i.e. `refs/heads/` or
358405
/// `refs/heads/feature-`.
359406
/// Note that if a prefix isn't using a trailing `/`, like in `refs/heads/foo`, it will effectively
Binary file not shown.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
6+
git checkout -q -b main
7+
git commit -q --allow-empty -m c1
8+
git branch dt1
9+
git branch d1
10+
git branch A
11+
12+
mkdir -p .git/refs/remotes/origin
13+
mkdir -p .git/refs/prefix/feature/sub/dir
14+
15+
cp .git/refs/heads/main .git/refs/remotes/origin/
16+
cp .git/refs/heads/main .git/refs/d1
17+
cp .git/refs/heads/main .git/refs/prefix/feature-suffix
18+
cp .git/refs/heads/main .git/refs/prefix/feature/sub/dir/algo
19+
20+
echo "ref: refs/remotes/origin/main" > .git/refs/remotes/origin/HEAD
21+
echo "notahexsha" > .git/refs/broken
22+
23+
git rev-parse HEAD > .git/JIRI_HEAD
24+
touch .git/SOME_ALL_CAPS_FILE
25+
touch .git/refs/SHOULD_BE_EXCLUDED_HEAD
26+
27+
cat <<EOF >> .git/FETCH_HEAD
28+
9064ea31fae4dc59a56bdd3a06c0ddc990ee689e branch 'main' of https://github.com/Byron/gitoxide
29+
1b8d9e6a408e480ae1912e919c37a26e5c46639d not-for-merge branch 'faster-discovery' of https://github.com/Byron/gitoxide
30+
43f695a9607f1f85f859f2ef944b785b5b6dd238 not-for-merge branch 'fix-823' of https://github.com/Byron/gitoxide
31+
96267708958ead2646aae8766a50fa060739003c not-for-merge branch 'fix-bare-with-index' of https://github.com/Byron/gitoxide
32+
1397e19375bb98522f951b8a452b08c1b35ffbac not-for-merge branch 'gix-archive' of https://github.com/Byron/gitoxide
33+
db71ec8b7c7f2730c47dde3bb662ab56ae89ae7d not-for-merge branch 'index-from-files' of https://github.com/Byron/gitoxide
34+
9f0c71917e57653d2e7121eae65d9385a188a8df not-for-merge branch 'moonwalk' of https://github.com/Byron/gitoxide
35+
44d2b67de5639d4ea3d08ab030ecfe4bdfc8cbfb not-for-merge branch 'release-gix' of https://github.com/Byron/gitoxide
36+
37c3d073b15dafcb52b2040e4b92a413c69a726d not-for-merge branch 'smart-release-without-git2' of https://github.com/Byron/gitoxide
37+
af3608ad397784795c3758a1ac99ec6a367de9be not-for-merge branch 'walk-with-commitgraph' of https://github.com/Byron/gitoxide
38+
EOF
39+
40+
git tag t1
41+
git tag -m "tag object" dt1

gix-ref/tests/refs/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ mod partialname {
3939
}
4040
mod namespace;
4141
mod packed;
42+
mod pseudo_refs;
4243
mod reference;
4344
mod store;
4445
mod transaction;

gix-ref/tests/refs/pseudo_refs.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use crate::file::store_at;
2+
3+
#[test]
4+
fn pseudo_refs_iterate_valid_pseudorefs() -> crate::Result {
5+
let store = store_at("make_pref_repository.sh")?;
6+
7+
let prefs = store
8+
.iter_pseudo_refs()?
9+
.map(Result::unwrap)
10+
.map(|r: gix_ref::Reference| r.name)
11+
.collect::<Vec<_>>();
12+
13+
let expected_prefs = vec!["FETCH_HEAD", "HEAD", "JIRI_HEAD"];
14+
15+
assert_eq!(
16+
prefs.iter().map(gix_ref::FullName::as_bstr).collect::<Vec<_>>(),
17+
expected_prefs
18+
);
19+
20+
Ok(())
21+
}

0 commit comments

Comments
 (0)