Skip to content

Commit c2cf20c

Browse files
committed
Merge branch 'head-conversions'
2 parents fb81093 + 21729ed commit c2cf20c

File tree

18 files changed

+567
-473
lines changed

18 files changed

+567
-473
lines changed

Cargo.lock

Lines changed: 370 additions & 379 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-ref/src/raw.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ pub struct Reference {
1010
pub name: FullName,
1111
/// The target of the reference, either a symbolic reference by full name or a possibly intermediate object by its id.
1212
pub target: Target,
13-
/// The fully peeled object to which this reference ultimately points to. Only guaranteed to be set after `peel_to_id_in_place()` was called.
13+
/// The fully peeled object to which this reference ultimately points to. Only guaranteed to be set after
14+
/// [`Reference::peel_to_id_in_place()`](crate::file::ReferenceExt) was called or if this reference originated
15+
/// from a packed ref.
1416
pub peeled: Option<ObjectId>,
1517
}
1618

gix-url/src/parse.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub(crate) fn url(input: &BStr, protocol_end: usize) -> Result<crate::Url, Error
8787
let bytes_to_path = input[protocol_end + "://".len()..]
8888
.iter()
8989
.filter(|b| !b.is_ascii_whitespace())
90-
.skip_while(|b| **b == b'/')
90+
.skip_while(|b| **b == b'/' || **b == b'\\')
9191
.position(|b| *b == b'/')
9292
.unwrap_or(input.len() - protocol_end);
9393
if bytes_to_path > MAX_LEN || protocol_end > MAX_LEN {
@@ -169,7 +169,10 @@ pub(crate) fn file_url(input: &BStr, protocol_colon: usize) -> Result<crate::Url
169169
let input = input_to_utf8(input, UrlKind::Url)?;
170170
let input_after_protocol = &input[protocol_colon + "://".len()..];
171171

172-
let Some(first_slash) = input_after_protocol.find('/') else {
172+
let Some(first_slash) = input_after_protocol
173+
.find('/')
174+
.or_else(|| cfg!(windows).then(|| input_after_protocol.find('\\')).flatten())
175+
else {
173176
return Err(Error::MissingRepositoryPath {
174177
url: input.to_owned().into(),
175178
kind: UrlKind::Url,
111 KB
Binary file not shown.

gix-url/tests/parse/file.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use assert_matches::assert_matches;
21
use bstr::ByteSlice;
32
use gix_url::Scheme;
43

@@ -93,11 +92,19 @@ fn no_relative_paths_if_protocol() -> crate::Result {
9392
assert_url_roundtrip("file://../", url(Scheme::File, None, "..", None, b"/"))?;
9493
assert_url_roundtrip("file://./", url(Scheme::File, None, ".", None, b"/"))?;
9594
assert_url_roundtrip("file://a/", url(Scheme::File, None, "a", None, b"/"))?;
96-
assert_matches!(
95+
if cfg!(windows) {
96+
assert_eq!(
97+
gix_url::parse("file://.\\".into())?,
98+
url(Scheme::File, None, ".", None, b"\\"),
99+
"we are just as none-sensical as git here due to special handling."
100+
);
101+
} else {
102+
assert_matches::assert_matches!(
97103
gix_url::parse("file://.\\".into()),
98104
Err(gix_url::parse::Error::MissingRepositoryPath { .. }),
99105
"DEVIATION: on windows, this parses with git into something nonesensical Diag: url=file://./ Diag: protocol=file Diag: hostandport=./ Diag: path=//./"
100106
);
107+
}
101108
Ok(())
102109
}
103110

@@ -122,6 +129,15 @@ mod windows {
122129
use crate::parse::{assert_url, assert_url_roundtrip, url, url_alternate};
123130
use gix_url::Scheme;
124131

132+
#[test]
133+
fn reproduce_1063() -> crate::Result {
134+
let input = "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\tmp.vIa4tyjv17";
135+
let url_input = "file://C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\tmp.vIa4tyjv17";
136+
assert_url(url_input, url(Scheme::File, None, None, None, input.as_bytes()))?;
137+
assert_url(input, url_alternate(Scheme::File, None, None, None, input.as_bytes()))?;
138+
Ok(())
139+
}
140+
125141
#[test]
126142
fn url_from_absolute_path() -> crate::Result {
127143
assert_url(

gix-url/tests/parse/invalid.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ fn empty_input() {
3333
fn file_missing_host_path_separator() {
3434
assert_matches!(parse("file://.."), Err(MissingRepositoryPath { .. }));
3535
assert_matches!(parse("file://."), Err(MissingRepositoryPath { .. }));
36-
assert_matches!(parse("file://.\\"), Err(MissingRepositoryPath { .. }));
3736
assert_matches!(parse("file://a"), Err(MissingRepositoryPath { .. }));
3837
}
3938

gix-url/tests/parse/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ mod unknown {
139139

140140
#[test]
141141
fn fuzzed() {
142-
for name in ["very-long4", "very-long3", "very-long2", "very-long"] {
142+
for name in ["very-long5", "very-long4", "very-long3", "very-long2", "very-long"] {
143143
let base = Path::new("tests").join("fixtures").join("fuzzed");
144144
let location = base.join(Path::new(name).with_extension("url"));
145145
let url = std::fs::read(&location).unwrap();

gix/examples/stats.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
1111
let mut most_recent_commit_id = None;
1212
let num_commits = repo
1313
.head()?
14-
.into_fully_peeled_id()
15-
.ok_or("Cannot provide meaningful stats on empty repos")??
14+
.try_into_peeled_id()?
15+
.ok_or("Cannot provide meaningful stats on empty repos")?
1616
.ancestors()
1717
.all()?
1818
.map_while(Result::ok)

gix/src/clone/checkout.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub mod main_worktree {
8787
let workdir = repo.work_dir().ok_or_else(|| Error::BareRepository {
8888
git_dir: repo.git_dir().to_owned(),
8989
})?;
90-
let root_tree = match repo.head()?.peel_to_id_in_place().transpose()? {
90+
let root_tree = match repo.head()?.try_peel_to_id_in_place()? {
9191
Some(id) => id.object().expect("downloaded from remote").peel_to_tree()?.id,
9292
None => {
9393
return Ok((

gix/src/head/peel.rs

Lines changed: 97 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use crate::{
22
ext::{ObjectIdExt, ReferenceExt},
3+
head::Kind,
34
Head,
45
};
56

67
mod error {
78
use crate::{object, reference};
89

9-
/// The error returned by [`Head::peel_to_id_in_place()`][super::Head::peel_to_id_in_place()] and [`Head::into_fully_peeled_id()`][super::Head::into_fully_peeled_id()].
10+
/// The error returned by [`Head::peel_to_id_in_place()`](super::Head::try_peel_to_id_in_place())
11+
/// and [`Head::into_fully_peeled_id()`](super::Head::try_into_peeled_id()).
1012
#[derive(Debug, thiserror::Error)]
1113
#[allow(missing_docs)]
1214
pub enum Error {
@@ -19,13 +21,11 @@ mod error {
1921

2022
pub use error::Error;
2123

22-
use crate::head::Kind;
23-
2424
///
25-
pub mod to_commit {
25+
pub mod into_id {
2626
use crate::object;
2727

28-
/// The error returned by [`Head::peel_to_commit_in_place()`][super::Head::peel_to_commit_in_place()].
28+
/// The error returned by [`Head::into_peeled_id()`](super::Head::into_peeled_id()).
2929
#[derive(Debug, thiserror::Error)]
3030
#[allow(missing_docs)]
3131
pub enum Error {
@@ -38,86 +38,122 @@ pub mod to_commit {
3838
}
3939
}
4040

41+
///
42+
pub mod to_commit {
43+
use crate::object;
44+
45+
/// The error returned by [`Head::peel_to_commit_in_place()`](super::Head::peel_to_commit_in_place()).
46+
#[derive(Debug, thiserror::Error)]
47+
#[allow(missing_docs)]
48+
pub enum Error {
49+
#[error(transparent)]
50+
PeelToObject(#[from] super::to_object::Error),
51+
#[error(transparent)]
52+
ObjectKind(#[from] object::try_into::Error),
53+
}
54+
}
55+
56+
///
57+
pub mod to_object {
58+
/// The error returned by [`Head::peel_to_object_in_place()`](super::Head::peel_to_object_in_place()).
59+
#[derive(Debug, thiserror::Error)]
60+
#[allow(missing_docs)]
61+
pub enum Error {
62+
#[error(transparent)]
63+
Peel(#[from] super::Error),
64+
#[error("Branch '{name}' does not have any commits")]
65+
Unborn { name: gix_ref::FullName },
66+
}
67+
}
68+
4169
impl<'repo> Head<'repo> {
42-
// TODO: tests
43-
/// Peel this instance to make obtaining its final target id possible, while returning an error on unborn heads.
44-
pub fn peeled(mut self) -> Result<Self, Error> {
45-
self.peel_to_id_in_place().transpose()?;
46-
Ok(self)
70+
/// Peel this instance and consume it to make obtaining its final target id possible, while returning an error on unborn heads.
71+
///
72+
/// The final target is obtained by following symbolic references and peeling tags to their final destination, which
73+
/// typically is a commit, but can be any object.
74+
pub fn into_peeled_id(mut self) -> Result<crate::Id<'repo>, into_id::Error> {
75+
self.try_peel_to_id_in_place()?;
76+
self.id().ok_or_else(|| match self.kind {
77+
Kind::Symbolic(gix_ref::Reference { name, .. }) | Kind::Unborn(name) => into_id::Error::Unborn { name },
78+
Kind::Detached { .. } => unreachable!("id can be returned after peeling"),
79+
})
80+
}
81+
82+
/// Peel this instance and consume it to make obtaining its final target object possible, while returning an error on unborn heads.
83+
///
84+
/// The final target is obtained by following symbolic references and peeling tags to their final destination, which
85+
/// typically is a commit, but can be any object as well.
86+
pub fn into_peeled_object(mut self) -> Result<crate::Object<'repo>, to_object::Error> {
87+
self.peel_to_object_in_place()
88+
}
89+
90+
/// Consume this instance and transform it into the final object that it points to, or `Ok(None)` if the `HEAD`
91+
/// reference is yet to be born.
92+
///
93+
/// The final target is obtained by following symbolic references and peeling tags to their final destination, which
94+
/// typically is a commit, but can be any object.
95+
pub fn try_into_peeled_id(mut self) -> Result<Option<crate::Id<'repo>>, Error> {
96+
self.try_peel_to_id_in_place()
4797
}
4898

49-
// TODO: tests
50-
// TODO: Fix this! It's not consistently peeling tags. The whole peeling business should be reconsidered to do what people usually
51-
// want which is to peel references, if present, and then peel objects with control over which object type to end at.
52-
// Finding a good interface for that isn't easy as ideally, it's an iterator that shows the intermediate objects so the user
53-
// can select which tag of a chain to choose.
5499
/// Follow the symbolic reference of this head until its target object and peel it by following tag objects until there is no
55100
/// more object to follow, and return that object id.
56101
///
57-
/// Returns `None` if the head is unborn.
58-
pub fn peel_to_id_in_place(&mut self) -> Option<Result<crate::Id<'repo>, Error>> {
59-
Some(match &mut self.kind {
60-
Kind::Unborn(_name) => return None,
102+
/// Returns `Ok(None)` if the head is unborn.
103+
///
104+
/// The final target is obtained by following symbolic references and peeling tags to their final destination, which
105+
/// typically is a commit, but can be any object.
106+
pub fn try_peel_to_id_in_place(&mut self) -> Result<Option<crate::Id<'repo>>, Error> {
107+
Ok(Some(match &mut self.kind {
108+
Kind::Unborn(_name) => return Ok(None),
61109
Kind::Detached {
62110
peeled: Some(peeled), ..
63-
} => Ok((*peeled).attach(self.repo)),
111+
} => (*peeled).attach(self.repo),
64112
Kind::Detached { peeled: None, target } => {
65-
match target
66-
.attach(self.repo)
67-
.object()
68-
.map_err(Into::into)
69-
.and_then(|obj| obj.peel_tags_to_end().map_err(Into::into))
70-
.map(|peeled| peeled.id)
71-
{
72-
Ok(peeled) => {
73-
self.kind = Kind::Detached {
74-
peeled: Some(peeled),
75-
target: *target,
76-
};
77-
Ok(peeled.attach(self.repo))
113+
let id = target.attach(self.repo);
114+
if id.header()?.kind() == gix_object::Kind::Commit {
115+
id
116+
} else {
117+
match id.object()?.peel_tags_to_end() {
118+
Ok(obj) => {
119+
self.kind = Kind::Detached {
120+
peeled: Some(obj.id),
121+
target: *target,
122+
};
123+
obj.id()
124+
}
125+
Err(err) => return Err(err.into()),
78126
}
79-
Err(err) => Err(err),
80127
}
81128
}
82129
Kind::Symbolic(r) => {
83130
let mut nr = r.clone().attach(self.repo);
84-
let peeled = nr.peel_to_id_in_place().map_err(Into::into);
131+
let peeled = nr.peel_to_id_in_place();
85132
*r = nr.detach();
86-
peeled
133+
peeled?
87134
}
88-
})
135+
}))
89136
}
90137

91-
// TODO: tests
92-
// TODO: something similar in `crate::Reference`
93138
/// Follow the symbolic reference of this head until its target object and peel it by following tag objects until there is no
94139
/// more object to follow, transform the id into a commit if possible and return that.
95140
///
96141
/// Returns an error if the head is unborn or if it doesn't point to a commit.
97-
pub fn peel_to_commit_in_place(&mut self) -> Result<crate::Commit<'repo>, to_commit::Error> {
98-
let id = self.peel_to_id_in_place().ok_or_else(|| to_commit::Error::Unborn {
99-
name: self.referent_name().expect("unborn").to_owned(),
100-
})??;
142+
pub fn peel_to_object_in_place(&mut self) -> Result<crate::Object<'repo>, to_object::Error> {
143+
let id = self
144+
.try_peel_to_id_in_place()?
145+
.ok_or_else(|| to_object::Error::Unborn {
146+
name: self.referent_name().expect("unborn").to_owned(),
147+
})?;
101148
id.object()
102-
.map_err(|err| to_commit::Error::Peel(Error::FindExistingObject(err)))
103-
.and_then(|object| object.try_into_commit().map_err(Into::into))
149+
.map_err(|err| to_object::Error::Peel(Error::FindExistingObject(err)))
104150
}
105151

106-
/// Consume this instance and transform it into the final object that it points to, or `None` if the `HEAD`
107-
/// reference is yet to be born.
108-
pub fn into_fully_peeled_id(self) -> Option<Result<crate::Id<'repo>, Error>> {
109-
Some(match self.kind {
110-
Kind::Unborn(_name) => return None,
111-
Kind::Detached {
112-
peeled: Some(peeled), ..
113-
} => Ok(peeled.attach(self.repo)),
114-
Kind::Detached { peeled: None, target } => target
115-
.attach(self.repo)
116-
.object()
117-
.map_err(Into::into)
118-
.and_then(|obj| obj.peel_tags_to_end().map_err(Into::into))
119-
.map(|obj| obj.id.attach(self.repo)),
120-
Kind::Symbolic(r) => r.attach(self.repo).peel_to_id_in_place().map_err(Into::into),
121-
})
152+
/// Follow the symbolic reference of this head until its target object and peel it by following tag objects until there is no
153+
/// more object to follow, transform the id into a commit if possible and return that.
154+
///
155+
/// Returns an error if the head is unborn or if it doesn't point to a commit.
156+
pub fn peel_to_commit_in_place(&mut self) -> Result<crate::Commit<'repo>, to_commit::Error> {
157+
Ok(self.peel_to_object_in_place()?.try_into_commit()?)
122158
}
123159
}

gix/src/reference/errors.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ pub mod head_id {
4343
#[error(transparent)]
4444
Head(#[from] crate::reference::find::existing::Error),
4545
#[error(transparent)]
46-
PeelToId(#[from] crate::head::peel::Error),
47-
#[error("Branch '{name}' does not have any commits")]
48-
Unborn { name: gix_ref::FullName },
46+
PeelToId(#[from] crate::head::peel::into_id::Error),
4947
}
5048
}
5149

@@ -69,9 +67,7 @@ pub mod head_tree_id {
6967
#[allow(missing_docs)]
7068
pub enum Error {
7169
#[error(transparent)]
72-
Head(#[from] crate::reference::find::existing::Error),
73-
#[error(transparent)]
74-
PeelToCommit(#[from] crate::head::peel::to_commit::Error),
70+
HeadCommit(#[from] crate::reference::head_commit::Error),
7571
#[error(transparent)]
7672
DecodeCommit(#[from] gix_object::decode::Error),
7773
}

gix/src/repository/reference.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -174,20 +174,16 @@ impl crate::Repository {
174174
.attach(self))
175175
}
176176

177-
/// Resolve the `HEAD` reference, follow and peel its target and obtain its object id.
177+
/// Resolve the `HEAD` reference, follow and peel its target and obtain its object id,
178+
/// following symbolic references and tags until a commit is found.
178179
///
179180
/// Note that this may fail for various reasons, most notably because the repository
180181
/// is freshly initialized and doesn't have any commits yet.
181182
///
182183
/// Also note that the returned id is likely to point to a commit, but could also
183184
/// point to a tree or blob. It won't, however, point to a tag as these are always peeled.
184185
pub fn head_id(&self) -> Result<crate::Id<'_>, reference::head_id::Error> {
185-
let mut head = self.head()?;
186-
head.peel_to_id_in_place()
187-
.ok_or_else(|| reference::head_id::Error::Unborn {
188-
name: head.referent_name().expect("unborn").to_owned(),
189-
})?
190-
.map_err(Into::into)
186+
Ok(self.head()?.into_peeled_id()?)
191187
}
192188

193189
/// Return the name to the symbolic reference `HEAD` points to, or `None` if the head is detached.
@@ -203,7 +199,8 @@ impl crate::Repository {
203199
Ok(self.head()?.try_into_referent())
204200
}
205201

206-
/// Return the commit object the `HEAD` reference currently points to after peeling it fully.
202+
/// Return the commit object the `HEAD` reference currently points to after peeling it fully,
203+
/// following symbolic references and tags until a commit is found.
207204
///
208205
/// Note that this may fail for various reasons, most notably because the repository
209206
/// is freshly initialized and doesn't have any commits yet. It could also fail if the
@@ -212,13 +209,14 @@ impl crate::Repository {
212209
Ok(self.head()?.peel_to_commit_in_place()?)
213210
}
214211

215-
/// Return the tree id the `HEAD` reference currently points to after peeling it fully.
212+
/// Return the tree id the `HEAD` reference currently points to after peeling it fully,
213+
/// following symbolic references and tags until a commit is found.
216214
///
217215
/// Note that this may fail for various reasons, most notably because the repository
218216
/// is freshly initialized and doesn't have any commits yet. It could also fail if the
219217
/// head does not point to a commit.
220218
pub fn head_tree_id(&self) -> Result<crate::Id<'_>, reference::head_tree_id::Error> {
221-
Ok(self.head()?.peel_to_commit_in_place()?.tree_id()?)
219+
Ok(self.head_commit()?.tree_id()?)
222220
}
223221

224222
/// Find the reference with the given partial or full `name`, like `main`, `HEAD`, `heads/branch` or `origin/other`,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:1de90b3e0c6547fda6759fb5df5e4bdf410dbe062ef9ef4f972308452c3224d7
3+
size 11508

0 commit comments

Comments
 (0)