version-rs/src/gitrev.rs

176 lines
4.9 KiB
Rust

use {
crate::{
error::Error, extended::Extended, rapid::Rapid, semver::SemVer, simple::Simple,
version::Kind, version::Version,
},
chrono::{offset::Utc, DateTime},
serde::{Deserialize, Serialize},
std::{cmp::Ordering, fmt, str::FromStr},
};
/// Represents a Git revisionrather than a regular release.
/// A `GitRev` release contains the revision's hash and the Date/Time of the release.
/// Git revisions are not the preferred method of distribution for obvious reasons, as
/// they generally do not represent a stable release of the code. Another drawback is
/// that a Git revision can only be compared against another Git revision, making it
/// impossible to properly order updates between a proper `SemVer` type release and a\
/// Git revision.
/// ### Notes on display formatting
/// The hash as stored in this struct should be the short form revision hash (the first
/// 7 characters of the full hash). The full date and time information is saved minus
/// any fractional seconds. When displaying, the date and time should be converted to a
/// Unix timestamp.
/// ### Parsing from a string
/// In order to parse this struct from a string, two fields `hash` and `datetime` should
/// be separated by a period character '.' and the date/time take the form of a Unix
/// timestamp. This information can be retrieved from git for a given commit using the
/// following git commandline:
/// ```Sh
/// git show --pretty=format:"%h.%at" <hash> | head -n 1
/// ```
/// The resulting string can then be parsed using the `FromStr` trait, which provides
/// both `from_str` and `parse`.
/// ### Comparison - ordering
/// Git revisions are ordered by date/time
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct GitRev {
/// the short revision hash
pub hash: String,
/// the time of the revision commit
pub datetime: DateTime<Utc>,
}
impl fmt::Display for GitRev {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "git_{}.{}", self.hash, self.datetime.timestamp())
}
}
impl PartialOrd for GitRev {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for GitRev {
fn cmp(&self, other: &Self) -> Ordering {
self.datetime.cmp(&other.datetime)
}
}
impl FromStr for GitRev {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(gitrev) = s.strip_prefix("git_") {
if let Some((hash, datetime)) = gitrev.split_once('.') {
if hash.len() == 7 {
let secs: i64 = datetime.parse()?;
let Some(datetime) = DateTime::<Utc>::from_timestamp(secs, 0) else {
return Err(Error::ParseInt);
};
return Ok(Self {
hash: hash.to_string(),
datetime,
});
}
}
}
Err(Error::ParseGitRev)
}
}
impl PartialEq<Extended> for GitRev {
fn eq(&self, _other: &Extended) -> bool {
false
}
}
impl PartialOrd<Extended> for GitRev {
fn partial_cmp(&self, _other: &Extended) -> Option<Ordering> {
None
}
}
impl PartialEq<SemVer> for GitRev {
fn eq(&self, _other: &SemVer) -> bool {
false
}
}
impl PartialOrd<SemVer> for GitRev {
fn partial_cmp(&self, _other: &SemVer) -> Option<Ordering> {
None
}
}
impl PartialEq<Rapid> for GitRev {
fn eq(&self, _other: &Rapid) -> bool {
false
}
}
impl PartialOrd<Rapid> for GitRev {
fn partial_cmp(&self, _other: &Rapid) -> Option<Ordering> {
None
}
}
impl PartialEq<Simple> for GitRev {
fn eq(&self, _other: &Simple) -> bool {
false
}
}
impl PartialOrd<Simple> for GitRev {
fn partial_cmp(&self, _other: &Simple) -> Option<Ordering> {
None
}
}
impl TryFrom<Version> for GitRev {
type Error = Error;
fn try_from(value: Version) -> Result<Self, Self::Error> {
match value.0 {
Kind::GitRev(g) => Ok(g),
_ => Err(Error::ParseGitRev),
}
}
}
#[cfg(test)]
mod test {
use std::{thread, time::Duration};
use super::*;
#[test]
fn ord() {
let a = GitRev {
hash: "aaabxxx".to_string(),
datetime: Utc::now(),
};
thread::sleep(Duration::from_millis(10));
let b = GitRev {
hash: "aaaaxxx".to_string(),
datetime: Utc::now(),
};
assert!(a < b);
}
#[test]
fn from_str() {
let now = DateTime::<Utc>::from_timestamp(Utc::now().timestamp(), 0).unwrap();
let rev: GitRev = format!("git_r2d2xxx.{}", now.timestamp()).parse().unwrap();
println!("Version = {rev}");
assert_eq!(
rev,
GitRev {
hash: "r2d2xxx".to_string(),
datetime: now,
}
);
}
}