use crate::config::{Config, Instructions};
use anyhow::Result;
use std::path::Path;
#[cfg(feature = "git")]
use {
crate::{
config::VergenKey,
error::Error,
feature::{self, add_entry, TimestampKind},
},
chrono::{DateTime, FixedOffset, Local, TimeZone, Utc},
getset::{Getters, MutGetters},
git2::{BranchType, DescribeOptions, Repository},
std::env,
};
#[cfg(feature = "git")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SemverKind {
Normal,
Lightweight,
}
#[cfg(feature = "git")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ShaKind {
Normal,
Short,
}
#[cfg_attr(feature = "git", doc = r##"use vergen::ShaKind;"##)]
#[cfg_attr(
feature = "git",
doc = r##"
// Change the SHA output to the short variant
*config.git_mut().sha_kind_mut() = ShaKind::Short;
// Generate the instructions
vergen(config)?;
"##
)]
#[cfg(feature = "git")]
#[derive(Clone, Copy, Debug, Getters, MutGetters)]
#[getset(get = "pub(crate)", get_mut = "pub")]
pub struct Git {
branch: bool,
commit_timestamp: bool,
commit_timestamp_timezone: feature::TimeZone,
commit_timestamp_kind: TimestampKind,
rerun_on_head_change: bool,
semver: bool,
semver_kind: SemverKind,
sha: bool,
sha_kind: ShaKind,
}
#[cfg(feature = "git")]
impl Default for Git {
fn default() -> Self {
Self {
branch: true,
commit_timestamp: true,
commit_timestamp_timezone: feature::TimeZone::Utc,
commit_timestamp_kind: TimestampKind::Timestamp,
rerun_on_head_change: true,
semver: true,
semver_kind: SemverKind::Normal,
sha: true,
sha_kind: ShaKind::Normal,
}
}
}
#[cfg(feature = "git")]
impl Git {
pub(crate) fn has_enabled(&self) -> bool {
self.branch || self.commit_timestamp || self.rerun_on_head_change || self.semver || self.sha
}
}
#[cfg(not(feature = "git"))]
pub(crate) fn configure_git<T>(
_instructions: Instructions,
_repo: Option<T>,
_config: &mut Config,
) -> Result<()>
where
T: AsRef<Path>,
{
Ok(())
}
#[cfg(feature = "git")]
pub(crate) fn configure_git<T>(
instructions: Instructions,
repo_path_opt: Option<T>,
config: &mut Config,
) -> Result<()>
where
T: AsRef<Path>,
{
if let Some(repo_path) = repo_path_opt {
let git_config = instructions.git();
if git_config.has_enabled() {
let repo = Repository::discover(repo_path)?;
let ref_head = repo.find_reference("HEAD")?;
let repo_path = repo.path().to_path_buf();
if *git_config.branch() {
add_branch_name(&repo, config)?;
}
if *git_config.commit_timestamp() || *git_config.sha() {
let commit = ref_head.peel_to_commit()?;
if *git_config.commit_timestamp() {
let offset = if commit.time().sign() == '-' {
FixedOffset::west(commit.time().offset_minutes() * 60)
.timestamp(commit.time().seconds(), 0)
} else {
FixedOffset::east(commit.time().offset_minutes() * 60)
.timestamp(commit.time().seconds(), 0)
};
match git_config.commit_timestamp_timezone() {
crate::TimeZone::Utc => {
add_config_entries(config, *git_config, &offset.with_timezone(&Utc))
}
crate::TimeZone::Local => {
add_config_entries(config, *git_config, &offset.with_timezone(&Local))
}
}
}
if *git_config.sha() {
match git_config.sha_kind() {
crate::ShaKind::Normal => {
add_entry(
config.cfg_map_mut(),
VergenKey::Sha,
Some(commit.id().to_string()),
);
}
crate::ShaKind::Short => {
let obj = repo.revparse_single("HEAD")?;
add_entry(
config.cfg_map_mut(),
VergenKey::ShortSha,
obj.short_id()?.as_str().map(str::to_string),
);
}
}
}
}
if *git_config.semver() {
match *git_config.semver_kind() {
crate::SemverKind::Normal => {
add_semver(&repo, &DescribeOptions::new(), false, config)
}
crate::SemverKind::Lightweight => {
let mut opts = DescribeOptions::new();
let _ = opts.describe_tags();
add_semver(&repo, &opts, true, config);
}
}
}
if let Ok(resolved) = ref_head.resolve() {
if let Some(name) = resolved.name() {
*config.ref_path_mut() = Some(repo_path.join(name));
}
}
*config.head_path_mut() = Some(repo_path.join("HEAD"));
}
}
Ok(())
}
#[cfg(feature = "git")]
fn add_config_entries<T>(config: &mut Config, git_config: Git, now: &DateTime<T>)
where
T: TimeZone,
T::Offset: std::fmt::Display,
{
match git_config.commit_timestamp_kind() {
TimestampKind::DateOnly => add_date_entry(config, now),
TimestampKind::TimeOnly => add_time_entry(config, now),
TimestampKind::DateAndTime => {
add_date_entry(config, now);
add_time_entry(config, now);
}
TimestampKind::Timestamp => add_timestamp_entry(config, now),
TimestampKind::All => {
add_date_entry(config, now);
add_time_entry(config, now);
add_timestamp_entry(config, now);
}
}
}
#[cfg(feature = "git")]
fn add_date_entry<T>(config: &mut Config, now: &DateTime<T>)
where
T: TimeZone,
T::Offset: std::fmt::Display,
{
add_entry(
config.cfg_map_mut(),
VergenKey::CommitDate,
Some(now.format("%Y-%m-%d").to_string()),
);
}
#[cfg(feature = "git")]
fn add_time_entry<T>(config: &mut Config, now: &DateTime<T>)
where
T: TimeZone,
T::Offset: std::fmt::Display,
{
add_entry(
config.cfg_map_mut(),
VergenKey::CommitTime,
Some(now.format("%H:%M:%S").to_string()),
);
}
#[cfg(feature = "git")]
fn add_timestamp_entry<T>(config: &mut Config, now: &DateTime<T>)
where
T: TimeZone,
T::Offset: std::fmt::Display,
{
add_entry(
config.cfg_map_mut(),
VergenKey::CommitTimestamp,
Some(now.to_rfc3339()),
);
}
#[cfg(feature = "git")]
fn add_branch_name(repo: &Repository, config: &mut Config) -> Result<()> {
if repo.head_detached()? {
add_entry(
config.cfg_map_mut(),
VergenKey::Branch,
Some("detached HEAD".to_string()),
);
} else {
let locals = repo.branches(Some(BranchType::Local))?;
for (local, _bt) in locals.filter_map(std::result::Result::ok) {
if local.is_head() {
if let Some(name) = local.name()? {
add_entry(
config.cfg_map_mut(),
VergenKey::Branch,
Some(name.to_string()),
);
}
}
}
}
Ok(())
}
#[cfg(feature = "git")]
fn add_semver(repo: &Repository, opts: &DescribeOptions, lw: bool, config: &mut Config) {
let key = if lw {
VergenKey::SemverLightweight
} else {
VergenKey::Semver
};
let semver: Option<String> = repo
.describe(opts)
.map_or_else(
|_| env::var("CARGO_PKG_VERSION").map_err(Error::from),
|x| x.format(None).map_err(Error::from),
)
.ok();
add_entry(config.cfg_map_mut(), key, semver);
}
#[cfg(all(test, feature = "git"))]
mod test {
use super::{SemverKind, ShaKind};
use crate::{
config::Instructions,
feature::{TimeZone, TimestampKind},
};
#[test]
fn git_config() {
let mut config = Instructions::default();
assert!(config.git().branch);
assert!(config.git().commit_timestamp);
assert_eq!(config.git().commit_timestamp_timezone, TimeZone::Utc);
assert_eq!(config.git().commit_timestamp_kind, TimestampKind::Timestamp);
assert!(config.git().rerun_on_head_change);
assert!(config.git().semver);
assert_eq!(config.git().semver_kind, SemverKind::Normal);
assert!(config.git().sha);
assert_eq!(config.git().sha_kind, ShaKind::Normal);
config.git_mut().commit_timestamp_kind = TimestampKind::All;
assert_eq!(config.git().commit_timestamp_kind, TimestampKind::All);
}
}
#[cfg(all(test, not(feature = "git")))]
mod test {}