Skip to content

Commit 77686db

Browse files
committed
feat: revision::Spec::path_and_mode()
Provide additional information about revspecs for use with worktree filters.
1 parent 21bea0f commit 77686db

File tree

9 files changed

+59
-7
lines changed

9 files changed

+59
-7
lines changed

Diff for: gix/src/ext/rev_spec.rs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ impl RevSpecExt for gix_revision::Spec {
1212
fn attach(self, repo: &crate::Repository) -> crate::revision::Spec<'_> {
1313
crate::revision::Spec {
1414
inner: self,
15+
path: None,
1516
first_ref: None,
1617
second_ref: None,
1718
repo,

Diff for: gix/src/revision/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub use gix_revision as plumbing;
77

88
///
99
pub mod walk;
10+
use crate::bstr::BString;
1011
pub use walk::iter::Walk;
1112

1213
///
@@ -22,6 +23,8 @@ pub mod spec;
2223
#[cfg(feature = "revision")]
2324
pub struct Spec<'repo> {
2425
pub(crate) inner: gix_revision::Spec,
26+
/// The path we encountered in the revspec, like `@:<path>` or `@..@~1:<path>`.
27+
pub(crate) path: Option<(BString, gix_object::tree::EntryMode)>,
2528
/// The first name of a reference as seen while parsing a `RevSpec`, for completeness.
2629
pub(crate) first_ref: Option<gix_ref::Reference>,
2730
/// The second name of a reference as seen while parsing a `RevSpec`, for completeness.

Diff for: gix/src/revision/spec/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::bstr::BStr;
12
use crate::{ext::ReferenceExt, revision::Spec, Id, Reference};
23

34
///
@@ -37,6 +38,7 @@ impl<'repo> Spec<'repo> {
3738
pub fn from_id(id: Id<'repo>) -> Self {
3839
Spec {
3940
inner: gix_revision::Spec::Include(id.inner),
41+
path: None,
4042
repo: id.repo,
4143
first_ref: None,
4244
second_ref: None,
@@ -62,6 +64,13 @@ impl<'repo> Spec<'repo> {
6264
)
6365
}
6466

67+
/// Return the path encountered in specs like `@:<path>` or `:<path>`, along with the kind of object it represents.
68+
///
69+
/// Note that there can only be one as paths always terminates further revspec parsing.
70+
pub fn path_and_mode(&self) -> Option<(&BStr, gix_object::tree::EntryMode)> {
71+
self.path.as_ref().map(|(p, mode)| (p.as_ref(), *mode))
72+
}
73+
6574
/// Return the name of the first reference we encountered while resolving the rev-spec, or `None` if a short hash
6675
/// was used. For example, `@` might yield `Some(HEAD)`, but `abcd` yields `None`.
6776
pub fn first_reference(&self) -> Option<&gix_ref::Reference> {

Diff for: gix/src/revision/spec/parse/delegate/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ impl<'repo> Delegate<'repo> {
1717
Delegate {
1818
refs: Default::default(),
1919
objs: Default::default(),
20+
paths: Default::default(),
2021
ambiguous_objects: Default::default(),
2122
idx: 0,
2223
kind: None,
@@ -100,6 +101,7 @@ impl<'repo> Delegate<'repo> {
100101

101102
let range = zero_or_one_objects_or_ambiguity_err(self.objs, self.prefix, self.err, self.repo)?;
102103
Ok(crate::revision::Spec {
104+
path: self.paths[0].take().or(self.paths[1].take()),
103105
first_ref: self.refs[0].take(),
104106
second_ref: self.refs[1].take(),
105107
inner: kind_to_spec(self.kind, range)?,

Diff for: gix/src/revision/spec/parse/delegate/navigate.rs

+17-3
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
121121
let lookup_path = |obj: &ObjectId| {
122122
let tree_id = peel(repo, obj, gix_object::Kind::Tree)?;
123123
if path.is_empty() {
124-
return Ok(tree_id);
124+
return Ok((tree_id, gix_object::tree::EntryKind::Tree.into()));
125125
}
126126
let mut tree = repo.find_object(tree_id)?.into_tree();
127127
let entry =
@@ -131,11 +131,17 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
131131
object: obj.attach(repo).shorten_or_id(),
132132
tree: tree_id.attach(repo).shorten_or_id(),
133133
})?;
134-
Ok(entry.object_id())
134+
Ok((entry.object_id(), entry.mode()))
135135
};
136136
for obj in objs.iter() {
137137
match lookup_path(obj) {
138-
Ok(replace) => replacements.push((*obj, replace)),
138+
Ok((replace, mode)) => {
139+
if !path.is_empty() {
140+
// Technically this is letting the last one win, but so be it.
141+
self.paths[self.idx] = Some((path.to_owned(), mode));
142+
}
143+
replacements.push((*obj, replace))
144+
}
139145
Err(err) => errors.push((*obj, err)),
140146
}
141147
}
@@ -306,6 +312,14 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
306312
self.objs[self.idx]
307313
.get_or_insert_with(HashSet::default)
308314
.insert(entry.id);
315+
316+
self.paths[self.idx] = Some((
317+
path.to_owned(),
318+
entry
319+
.mode
320+
.to_tree_entry_mode()
321+
.unwrap_or(gix_object::tree::EntryKind::Blob.into()),
322+
));
309323
Some(())
310324
}
311325
None => {

Diff for: gix/src/revision/spec/parse/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use gix_revision::spec::parse;
77
use crate::{bstr::BStr, revision::Spec, Repository};
88

99
mod types;
10+
use crate::bstr::BString;
1011
pub use types::{Error, ObjectKindHint, Options, RefsHint};
1112

1213
///
@@ -45,6 +46,9 @@ impl<'repo> Spec<'repo> {
4546
struct Delegate<'repo> {
4647
refs: [Option<gix_ref::Reference>; 2],
4748
objs: [Option<HashSet<ObjectId>>; 2],
49+
/// Path specified like `@:<path>` or `:<path>` for later use when looking up specs.
50+
/// Note that it terminates spec parsing, so it's either `0` or `1`, never both.
51+
paths: [Option<(BString, gix_object::tree::EntryMode)>; 2],
4852
/// The originally encountered ambiguous objects for potential later use in errors.
4953
ambiguous_objects: [Option<HashSet<ObjectId>>; 2],
5054
idx: usize,

Diff for: gix/tests/revision/spec/from_bytes/ambiguous.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,16 @@ fn blob_and_tree_can_be_disambiguated_by_type() {
106106
#[test]
107107
fn trees_can_be_disambiguated_by_blob_access() {
108108
let repo = repo("ambiguous_blob_tree_commit").unwrap();
109+
let actual = parse_spec_better_than_baseline("0000000000:a0blgqsjc", &repo).unwrap();
109110
assert_eq!(
110-
parse_spec_better_than_baseline("0000000000:a0blgqsjc", &repo).unwrap(),
111+
actual,
111112
Spec::from_id(hex_to_id("0000000000b36b6aa7ea4b75318ed078f55505c3").attach(&repo)),
112113
"we can disambiguate by providing a path, but git cannot"
113114
);
115+
assert_eq!(
116+
actual.path_and_mode().expect("set"),
117+
("a0blgqsjc".into(), gix_object::tree::EntryKind::Blob.into())
118+
);
114119
}
115120

116121
#[test]

Diff for: gix/tests/revision/spec/from_bytes/mod.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,16 @@ mod index {
4343
#[test]
4444
fn at_stage() {
4545
let repo = repo("complex_graph").unwrap();
46+
let actual = parse_spec(":file", &repo).unwrap();
4647
assert_eq!(
47-
parse_spec(":file", &repo).unwrap(),
48+
actual,
4849
Spec::from_id(hex_to_id("fe27474251f7f8368742f01fbd3bd5666b630a82").attach(&repo))
4950
);
51+
assert_eq!(
52+
actual.path_and_mode().expect("set"),
53+
("file".into(), gix_object::tree::EntryKind::Blob.into()),
54+
"index paths (that are present) are captured"
55+
);
5056

5157
assert_eq!(
5258
parse_spec(":1:file", &repo).unwrap_err().to_string(),
@@ -116,10 +122,16 @@ fn bad_objects_are_valid_until_they_are_actually_read_from_the_odb() {
116122
#[test]
117123
fn access_blob_through_tree() {
118124
let repo = repo("ambiguous_blob_tree_commit").unwrap();
125+
let actual = parse_spec("0000000000cdc:a0blgqsjc", &repo).unwrap();
119126
assert_eq!(
120-
parse_spec("0000000000cdc:a0blgqsjc", &repo).unwrap(),
127+
actual,
121128
Spec::from_id(hex_to_id("0000000000b36b6aa7ea4b75318ed078f55505c3").attach(&repo))
122129
);
130+
assert_eq!(
131+
actual.path_and_mode().expect("set"),
132+
("a0blgqsjc".into(), gix_object::tree::EntryKind::Blob.into()),
133+
"we capture tree-paths"
134+
);
123135

124136
assert_eq!(
125137
parse_spec("0000000000cdc:missing", &repo).unwrap_err().to_string(),

Diff for: gix/tests/revision/spec/from_bytes/peel.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@ fn peel_to_object() {
2121
#[test]
2222
fn trailing_colon_is_equivalent_to_peel_to_tree() {
2323
let repo = &repo("complex_graph").unwrap();
24-
assert_eq!(parse_spec("@^{tree}", repo).unwrap(), parse_spec("@:", repo).unwrap());
24+
let empty_path = parse_spec("@:", repo).unwrap();
25+
assert_eq!(parse_spec("@^{tree}", repo).unwrap(), empty_path);
26+
assert_eq!(empty_path.path_and_mode(), None, "empty tree paths are ignored");
2527
}

0 commit comments

Comments
 (0)