Skip to content

Commit 56d7ad7

Browse files
committed
erge branch 'fix-describe'
2 parents 5ee2307 + a1680b4 commit 56d7ad7

File tree

10 files changed

+112
-36
lines changed

10 files changed

+112
-36
lines changed

git-object/src/commit/ref_iter.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ impl<'a> CommitRefIter<'a> {
7777
}
7878

7979
/// Returns the committer signature if there is no decoding error.
80-
/// Errors are coerced into options, hiding whether there was an error or not. The caller knows if there was an error or not.
8180
pub fn committer(mut self) -> Result<git_actor::SignatureRef<'a>, crate::decode::Error> {
8281
self.find_map(|t| match t {
8382
Ok(Token::Committer { signature }) => Some(Ok(signature)),
@@ -90,7 +89,6 @@ impl<'a> CommitRefIter<'a> {
9089
/// Returns the author signature if there is no decoding error.
9190
///
9291
/// It may contain white space surrounding it, and is exactly as parsed.
93-
/// Errors are coerced into options, hiding whether there was an error or not. The caller knows if there was an error or not.
9492
pub fn author(mut self) -> Result<git_actor::SignatureRef<'a>, crate::decode::Error> {
9593
self.find_map(|t| match t {
9694
Ok(Token::Author { signature }) => Some(Ok(signature)),
@@ -104,7 +102,6 @@ impl<'a> CommitRefIter<'a> {
104102
///
105103
/// It may contain white space surrounding it, and is exactly as
106104
// parsed.
107-
/// Errors are coerced into options, hiding whether there was an error or not. The caller knows if there was an error or not.
108105
pub fn message(mut self) -> Result<&'a BStr, crate::decode::Error> {
109106
self.find_map(|t| match t {
110107
Ok(Token::Message(msg)) => Some(Ok(msg)),

git-object/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ pub struct TagRef<'a> {
148148

149149
/// Like [`TagRef`], but as `Iterator` to support entirely allocation free parsing.
150150
/// It's particularly useful to dereference only the target chain.
151+
#[derive(Copy, Clone)]
151152
pub struct TagRefIter<'a> {
152153
data: &'a [u8],
153154
state: tag::ref_iter::State,

git-object/src/tag/ref_iter.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use nom::{
99

1010
use crate::{bstr::ByteSlice, parse, parse::NL, tag::decode, Kind, TagRefIter};
1111

12+
#[derive(Copy, Clone)]
1213
pub(crate) enum State {
1314
Target,
1415
TargetKind,
@@ -39,10 +40,21 @@ impl<'a> TagRefIter<'a> {
3940
/// Errors are coerced into options, hiding whether there was an error or not. The caller should assume an error if they
4041
/// call the method as intended. Such a squelched error cannot be recovered unless the objects data is retrieved and parsed again.
4142
/// `next()`.
42-
pub fn target_id(&mut self) -> Result<ObjectId, crate::decode::Error> {
43+
pub fn target_id(mut self) -> Result<ObjectId, crate::decode::Error> {
4344
let token = self.next().ok_or_else(missing_field)??;
4445
Token::into_id(token).ok_or_else(missing_field)
4546
}
47+
48+
/// Returns the taggers signature if there is no decoding error, and if this field exists.
49+
/// Errors are coerced into options, hiding whether there was an error or not. The caller knows if there was an error or not.
50+
pub fn tagger(mut self) -> Result<Option<git_actor::SignatureRef<'a>>, crate::decode::Error> {
51+
self.find_map(|t| match t {
52+
Ok(Token::Tagger(signature)) => Some(Ok(signature)),
53+
Err(err) => Some(Err(err)),
54+
_ => None,
55+
})
56+
.ok_or_else(missing_field)?
57+
}
4658
}
4759

4860
fn missing_field() -> crate::decode::Error {

git-object/tests/immutable/tag.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,21 @@ mod iter {
2626

2727
#[test]
2828
fn empty() -> crate::Result {
29+
let tag = fixture_bytes("tag", "empty.txt");
30+
let tag_iter = TagRefIter::from_bytes(&tag);
31+
let target_id = hex_to_id("01dd4e2a978a9f5bd773dae6da7aa4a5ac1cdbbc");
32+
let tagger = Some(signature(1592381636));
2933
assert_eq!(
30-
TagRefIter::from_bytes(&fixture_bytes("tag", "empty.txt")).collect::<Result<Vec<_>, _>>()?,
34+
tag_iter.collect::<Result<Vec<_>, _>>()?,
3135
vec![
32-
Token::Target {
33-
id: hex_to_id("01dd4e2a978a9f5bd773dae6da7aa4a5ac1cdbbc")
34-
},
36+
Token::Target { id: target_id },
3537
Token::TargetKind(Kind::Commit),
3638
Token::Name(b"empty".as_bstr()),
37-
Token::Tagger(Some(signature(1592381636))),
39+
Token::Tagger(tagger),
3840
]
3941
);
42+
assert_eq!(tag_iter.target_id()?, target_id);
43+
assert_eq!(tag_iter.tagger()?, tagger);
4044
Ok(())
4145
}
4246

git-repository/src/commit.rs

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub mod describe {
5555
}
5656

5757
/// A selector to choose what kind of references should contribute to names.
58+
#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)]
5859
pub enum SelectRef {
5960
/// Only use annotated tags for names.
6061
AnnotatedTags,
@@ -70,33 +71,60 @@ pub mod describe {
7071
repo: &Repository,
7172
) -> Result<git_revision::hash_hasher::HashedMap<ObjectId, Cow<'static, BStr>>, Error> {
7273
let platform = repo.references()?;
73-
let into_tuple =
74-
|r: crate::Reference<'_>| (r.inner.target.into_id(), Cow::from(r.inner.name.shorten().to_owned()));
7574

7675
Ok(match self {
77-
SelectRef::AllTags => platform
78-
.tags()?
79-
.peeled()
76+
SelectRef::AllTags | SelectRef::AllRefs => {
77+
let mut refs: Vec<_> = match self {
78+
SelectRef::AllRefs => platform.all()?,
79+
SelectRef::AllTags => platform.tags()?,
80+
_ => unreachable!(),
81+
}
8082
.filter_map(Result::ok)
81-
.map(into_tuple)
82-
.collect(),
83-
SelectRef::AllRefs => platform
84-
.all()?
85-
.peeled()
86-
.filter_map(Result::ok)
87-
.map(into_tuple)
88-
.collect(),
89-
SelectRef::AnnotatedTags => platform
90-
.tags()?
91-
.filter_map(Result::ok)
92-
.filter_map(|r: crate::Reference<'_>| {
93-
// TODO: we assume direct refs for tags, which is the common case, but it doesn't have to be
94-
// so rather follow symrefs till the first object and then peel tags after the first object was found.
95-
let tag = r.try_id()?.object().ok()?.try_into_tag().ok()?;
96-
let commit_id = tag.target_id().ok()?.object().ok()?.try_into_commit().ok()?.id;
97-
Some((commit_id, r.name().shorten().to_owned().into()))
83+
.filter_map(|mut r: crate::Reference<'_>| {
84+
let target_id = r.target().try_id().map(ToOwned::to_owned);
85+
let peeled_id = r.peel_to_id_in_place().ok()?;
86+
let (prio, tag_time) = match target_id {
87+
Some(target_id) if peeled_id != *target_id => {
88+
let tag = repo.find_object(target_id).ok()?.try_into_tag().ok()?;
89+
(1, tag.tagger().ok()??.time.seconds_since_unix_epoch)
90+
}
91+
_ => (0, 0),
92+
};
93+
(
94+
peeled_id.inner,
95+
prio,
96+
tag_time,
97+
Cow::from(r.inner.name.shorten().to_owned()),
98+
)
99+
.into()
98100
})
99-
.collect(),
101+
.collect();
102+
refs.sort_by(|a, b| a.2.cmp(&b.2).then_with(|| a.1.cmp(&b.1))); // by time ascending, then by priority. Older entries overwrite newer ones.
103+
refs.into_iter().map(|(a, _, _, b)| (a, b)).collect()
104+
}
105+
SelectRef::AnnotatedTags => {
106+
let mut peeled_commits_and_tag_date: Vec<_> = platform
107+
.tags()?
108+
.filter_map(Result::ok)
109+
.filter_map(|r: crate::Reference<'_>| {
110+
// TODO: we assume direct refs for tags, which is the common case, but it doesn't have to be
111+
// so rather follow symrefs till the first object and then peel tags after the first object was found.
112+
let tag = r.try_id()?.object().ok()?.try_into_tag().ok()?;
113+
let tag_time = tag
114+
.tagger()
115+
.ok()
116+
.and_then(|s| s.map(|s| s.time.seconds_since_unix_epoch))
117+
.unwrap_or(0);
118+
let commit_id = tag.target_id().ok()?.object().ok()?.try_into_commit().ok()?.id;
119+
Some((commit_id, tag_time, r.name().shorten().to_owned().into()))
120+
})
121+
.collect();
122+
peeled_commits_and_tag_date.sort_by(|a, b| a.1.cmp(&b.1)); // by time, ascending, causing older names to overwrite newer ones.
123+
peeled_commits_and_tag_date
124+
.into_iter()
125+
.map(|(a, _, c)| (a, c))
126+
.collect()
127+
}
100128
})
101129
}
102130
}
@@ -146,15 +174,15 @@ pub mod describe {
146174
/// if one was found.
147175
///
148176
/// Note that there will always be `Some(format)`
149-
pub fn try_format(self) -> Result<Option<git_revision::describe::Format<'static>>, Error> {
177+
pub fn try_format(&self) -> Result<Option<git_revision::describe::Format<'static>>, Error> {
150178
self.try_resolve()?.map(|r| r.format()).transpose()
151179
}
152180

153181
/// Try to find a name for the configured commit id using all prior configuration, returning `Some(Outcome)`
154182
/// if one was found.
155183
///
156184
/// The outcome provides additional information, but leaves the caller with the burden
157-
pub fn try_resolve(self) -> Result<Option<crate::commit::describe::Resolution<'repo>>, Error> {
185+
pub fn try_resolve(&self) -> Result<Option<crate::commit::describe::Resolution<'repo>>, Error> {
158186
// TODO: dirty suffix with respective dirty-detection
159187
let outcome = git_revision::describe(
160188
&self.id,
@@ -180,7 +208,7 @@ pub mod describe {
180208
}
181209

182210
/// Like [`try_format()`][Platform::try_format()], but turns `id_as_fallback()` on to always produce a format.
183-
pub fn format(mut self) -> Result<git_revision::describe::Format<'static>, Error> {
211+
pub fn format(&mut self) -> Result<git_revision::describe::Format<'static>, Error> {
184212
self.id_as_fallback = true;
185213
Ok(self.try_format()?.expect("BUG: fallback must always produce a format"))
186214
}

git-repository/src/object/tag.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
use crate::{ext::ObjectIdExt, Tag};
22

33
impl<'repo> Tag<'repo> {
4-
/// Decode this tag and return the id of its target.
4+
/// Decode this tag partially and return the id of its target.
55
pub fn target_id(&self) -> Result<crate::Id<'repo>, git_object::decode::Error> {
66
git_object::TagRefIter::from_bytes(&self.data)
77
.target_id()
88
.map(|id| id.attach(self.repo))
99
}
10+
11+
/// Decode this tag partially and return the tagger, if the field exists.
12+
pub fn tagger(&self) -> Result<Option<git_actor::SignatureRef<'_>>, git_object::decode::Error> {
13+
git_object::TagRefIter::from_bytes(&self.data).tagger()
14+
}
1015
}

git-repository/tests/commit/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
mod describe {
2+
use crate::named_repo;
3+
use git_repository::commit::describe::SelectRef::{AllRefs, AllTags, AnnotatedTags};
4+
5+
#[test]
6+
fn tags_are_sorted_by_date_and_lexigraphically() {
7+
let repo = named_repo("make_commit_describe_multiple_tags.sh").unwrap();
8+
let mut describe = repo.head_commit().unwrap().describe();
9+
for filter in &[AnnotatedTags, AllTags, AllRefs] {
10+
describe = describe.names(*filter);
11+
assert_eq!(describe.format().unwrap().to_string(), "v2", "{:?}", filter);
12+
}
13+
}
14+
}
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:22fd60100187fc3f4180b31d3ae1d8c5506a65c3ee059b4bbc3a40009fb23f60
3+
size 10408
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
git commit --allow-empty -q -m c1
6+
git commit --allow-empty -q -m c2
7+
8+
git tag v0 -m "tag object 0" "HEAD~1"
9+
git tag v1 -m "tag object 1"
10+
git tag v1.5
11+
GIT_COMMITTER_DATE="2022-01-02 00:00:00 +0000" git tag v2 -m "tag object 2"

git-repository/tests/git.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ fn basic_rw_repo() -> crate::Result<(Repository, tempfile::TempDir)> {
2828
repo_rw("make_basic_repo.sh")
2929
}
3030

31+
mod commit;
3132
mod discover;
3233
mod id;
3334
mod init;

0 commit comments

Comments
 (0)