1use chrono::{DateTime, TimeZone, Utc};
4
5use crate::Error;
6
7pub fn tag_date(
17 repo_path: &std::path::Path,
18 tag_name: &str,
19) -> Result<DateTime<Utc>, Error> {
20 let repo = gix::discover(repo_path).map_err(|e| Error::OpenRepo {
21 path: repo_path.to_owned(),
22 source: Box::new(e),
23 })?;
24
25 let ref_name = format!("refs/tags/{tag_name}");
26 let tag_ref = repo
27 .try_find_reference(&ref_name)
28 .map_err(|e| Error::Object(e.to_string()))?
29 .ok_or_else(|| Error::TagNotFound(tag_name.to_owned()))?;
30
31 let target_id = tag_ref.id();
32 let obj = repo
33 .find_object(target_id)
34 .map_err(|e| Error::Object(e.to_string()))?;
35
36 let seconds = resolve_seconds(&repo, obj)?;
37 Utc.timestamp_opt(seconds, 0)
38 .single()
39 .ok_or_else(|| Error::Object("invalid timestamp".into()))
40}
41
42fn resolve_seconds(
47 repo: &gix::Repository,
48 obj: gix::Object<'_>,
49) -> Result<i64, Error> {
50 match obj.kind {
51 gix::object::Kind::Tag => {
52 let tag = obj
53 .try_into_tag()
54 .map_err(|e| Error::Object(e.to_string()))?;
55 let tagger = tag
56 .tagger()
57 .map_err(|e| Error::Object(e.to_string()))?
58 .ok_or_else(|| Error::Object("annotated tag has no tagger field".into()))?;
59 let time = tagger
60 .time()
61 .map_err(|e| Error::Object(e.to_string()))?;
62 Ok(time.seconds)
63 }
64 gix::object::Kind::Commit => {
65 let commit = obj
66 .try_into_commit()
67 .map_err(|e| Error::Object(e.to_string()))?;
68 let time = commit
69 .time()
70 .map_err(|e| Error::Object(e.to_string()))?;
71 Ok(time.seconds)
72 }
73 _ => {
74 let commit = repo
75 .find_object(obj.id)
76 .map_err(|e| Error::Object(e.to_string()))?
77 .peel_to_commit()
78 .map_err(|e| Error::Object(e.to_string()))?;
79 let time = commit
80 .time()
81 .map_err(|e| Error::Object(e.to_string()))?;
82 Ok(time.seconds)
83 }
84 }
85}