use self::gix_builder::Empty;
use anyhow::{Error, Result, anyhow};
use bon::Builder;
use gix::{
Commit, Head, Id, Repository,
commit::describe::SelectRef,
dir::{entry::Status, walk::EmissionMode},
discover, head,
};
use std::{
env,
path::{Path, PathBuf},
};
use time::{
OffsetDateTime, UtcOffset,
format_description::{self, well_known::Iso8601},
};
use vergen_lib::{
AddEntries, CargoRerunIfChanged, CargoRustcEnvMap, CargoWarning, DefaultConfig, Describe,
Dirty, Sha, VergenKey, add_default_map_entry, add_map_entry,
constants::{
GIT_BRANCH_NAME, GIT_COMMIT_AUTHOR_EMAIL, GIT_COMMIT_AUTHOR_NAME, GIT_COMMIT_COUNT,
GIT_COMMIT_DATE_NAME, GIT_COMMIT_MESSAGE, GIT_COMMIT_TIMESTAMP_NAME,
GIT_COMMIT_TIMESTAMP_UNIX_NAME, GIT_DESCRIBE_NAME, GIT_DIRTY_NAME, GIT_SHA_NAME,
},
};
#[cfg(feature = "allow_remote")]
use {
gix::{clone::PrepareFetch, create, open, progress},
std::{env::temp_dir, sync::atomic::AtomicBool},
};
#[derive(Builder, Clone, Debug, PartialEq)]
#[allow(clippy::struct_excessive_bools)]
pub struct Gix {
#[builder(field)]
all: bool,
#[builder(into)]
local_repo_path: Option<PathBuf>,
#[builder(default = false)]
force_local: bool,
#[cfg(test)]
#[builder(default = false)]
force_remote: bool,
#[builder(into)]
remote_url: Option<String>,
#[builder(into)]
remote_repo_path: Option<PathBuf>,
#[builder(into)]
remote_tag: Option<String>,
#[cfg(feature = "vcs_info")]
#[builder(default = false)]
vcs_info_fallback: bool,
#[builder(default = all)]
branch: bool,
#[builder(default = all)]
commit_author_name: bool,
#[builder(default = all)]
commit_author_email: bool,
#[builder(default = all)]
commit_count: bool,
#[builder(default = all)]
commit_message: bool,
#[builder(default = all)]
commit_date: bool,
#[builder(default = all)]
commit_timestamp: bool,
#[builder(default = false)]
commit_timestamp_unix: bool,
#[builder(
required,
default = all.then(|| Describe::builder().build()),
with = |tags: bool, dirty: bool, match_pattern: Option<&'static str>| {
Some(Describe::builder().tags(tags).dirty(dirty).maybe_match_pattern(match_pattern).build())
}
)]
describe: Option<Describe>,
#[builder(
required,
default = all.then(|| Sha::builder().build()),
with = |short: bool| Some(Sha::builder().short(short).build())
)]
sha: Option<Sha>,
#[builder(
required,
default = all.then(|| Dirty::builder().build()),
with = |include_untracked: bool| Some(Dirty::builder().include_untracked(include_untracked).build())
)]
dirty: Option<Dirty>,
#[builder(default = false)]
use_local: bool,
}
impl<S: gix_builder::State> GixBuilder<S> {
fn all(mut self) -> Self {
self.all = true;
self
}
}
impl Gix {
#[must_use]
pub fn all_git() -> Self {
Self::builder().all().build()
}
pub fn all() -> GixBuilder<Empty> {
Self::builder().all()
}
fn any(&self) -> bool {
self.branch
|| self.commit_author_email
|| self.commit_author_name
|| self.commit_count
|| self.commit_date
|| self.commit_message
|| self.commit_timestamp
|| self.commit_timestamp_unix
|| self.describe.is_some()
|| self.sha.is_some()
|| self.dirty.is_some()
}
pub fn at_path(&mut self, path: PathBuf) -> &mut Self {
self.local_repo_path = Some(path);
self
}
fn add_entries(
&self,
idempotent: bool,
cargo_rustc_env: &mut CargoRustcEnvMap,
cargo_rerun_if_changed: &mut CargoRerunIfChanged,
cargo_warning: &mut CargoWarning,
) -> Result<()> {
if self.any() {
self.inner_add_git_map_entries(
idempotent,
cargo_rustc_env,
cargo_rerun_if_changed,
cargo_warning,
)?;
}
Ok(())
}
#[cfg(all(not(test), feature = "allow_remote"))]
#[allow(clippy::unused_self)]
fn try_local(&self) -> bool {
true
}
#[cfg(all(test, feature = "allow_remote"))]
fn try_local(&self) -> bool {
self.force_local || !self.force_remote
}
#[cfg(all(not(test), feature = "allow_remote"))]
fn try_remote(&self) -> bool {
!self.force_local
}
#[cfg(all(test, feature = "allow_remote"))]
fn try_remote(&self) -> bool {
self.force_remote || !self.force_local
}
#[cfg(not(feature = "allow_remote"))]
#[allow(clippy::unused_self)]
fn get_repository(
&self,
repo_dir: &PathBuf,
_warnings: &mut CargoWarning,
) -> Result<Repository> {
discover(repo_dir).map_err(Into::into)
}
#[cfg(feature = "allow_remote")]
fn get_repository(
&self,
repo_dir: &PathBuf,
warnings: &mut CargoWarning,
) -> Result<Repository> {
if self.try_local()
&& let Ok(repo) = discover(repo_dir)
{
Ok(repo)
} else if self.try_remote()
&& let Some(remote_url) = &self.remote_url
{
let repo_path = if let Some(path) = &self.remote_repo_path {
path.clone()
} else {
temp_dir().join("vergen-gix")
};
let mut fetch = PrepareFetch::new(
&remote_url[..],
repo_path,
create::Kind::Bare,
create::Options::default(),
open::Options::default(),
)?;
if let Some(remote_tag) = self.remote_tag.as_deref() {
fetch = fetch.with_ref_name(Some(remote_tag))?;
}
let (repo, _) = fetch.fetch_only(progress::Discard, &AtomicBool::default())?;
warnings.push(format!(
"Using remote repository from '{remote_url}' at '{}'",
repo.path().display()
));
Ok(repo)
} else {
Err(anyhow!(
"Could not find a git repository at '{}'",
repo_dir.display()
))
}
}
#[cfg(not(feature = "allow_remote"))]
#[allow(clippy::unused_self)]
fn cleanup(&self) {}
#[cfg(feature = "allow_remote")]
fn cleanup(&self) {
if let Some(_remote_url) = self.remote_url.as_ref() {
let temp_dir = temp_dir().join("vergen-gix");
if let Some(path) = &self.remote_repo_path {
if path.exists() {
let _ = std::fs::remove_dir_all(path).ok();
}
} else if temp_dir.exists() {
let _ = std::fs::remove_dir_all(temp_dir).ok();
}
}
}
#[allow(clippy::too_many_lines, clippy::default_trait_access)]
fn inner_add_git_map_entries(
&self,
idempotent: bool,
cargo_rustc_env: &mut CargoRustcEnvMap,
cargo_rerun_if_changed: &mut CargoRerunIfChanged,
cargo_warning: &mut CargoWarning,
) -> Result<()> {
let repo_dir = if let Some(path) = &self.local_repo_path {
path.clone()
} else {
env::current_dir()?
};
let repo = self.get_repository(&repo_dir, cargo_warning)?;
let mut head = repo.head()?;
let git_path = repo.git_dir().to_path_buf();
let commit = Self::get_commit(&repo, &mut head)?;
if !idempotent && self.any() {
Self::add_rerun_if_changed(&head, &git_path, cargo_rerun_if_changed);
}
if self.branch {
if let Ok(_value) = env::var(GIT_BRANCH_NAME) {
add_default_map_entry(
idempotent,
VergenKey::GitBranch,
cargo_rustc_env,
cargo_warning,
);
} else {
let branch_name = head
.referent_name()
.map_or_else(|| "HEAD".to_string(), |name| format!("{}", name.shorten()));
add_map_entry(VergenKey::GitBranch, branch_name, cargo_rustc_env);
}
}
if self.commit_author_email {
if let Ok(_value) = env::var(GIT_COMMIT_AUTHOR_EMAIL) {
add_default_map_entry(
idempotent,
VergenKey::GitCommitAuthorEmail,
cargo_rustc_env,
cargo_warning,
);
} else {
let email = String::from_utf8_lossy(commit.author()?.email);
add_map_entry(
VergenKey::GitCommitAuthorEmail,
email.into_owned(),
cargo_rustc_env,
);
}
}
if self.commit_author_name {
if let Ok(_value) = env::var(GIT_COMMIT_AUTHOR_NAME) {
add_default_map_entry(
idempotent,
VergenKey::GitCommitAuthorName,
cargo_rustc_env,
cargo_warning,
);
} else {
let name = String::from_utf8_lossy(commit.author()?.name);
add_map_entry(
VergenKey::GitCommitAuthorName,
name.into_owned(),
cargo_rustc_env,
);
}
}
if self.commit_count {
if let Ok(_value) = env::var(GIT_COMMIT_COUNT) {
add_default_map_entry(
idempotent,
VergenKey::GitCommitCount,
cargo_rustc_env,
cargo_warning,
);
} else {
add_map_entry(
VergenKey::GitCommitCount,
commit.ancestors().all()?.count().to_string(),
cargo_rustc_env,
);
}
}
self.add_git_timestamp_entries(idempotent, &commit, cargo_rustc_env, cargo_warning)?;
if self.commit_message {
if let Ok(_value) = env::var(GIT_COMMIT_MESSAGE) {
add_default_map_entry(
idempotent,
VergenKey::GitCommitMessage,
cargo_rustc_env,
cargo_warning,
);
} else {
let message = String::from_utf8_lossy(commit.message_raw()?);
add_map_entry(
VergenKey::GitCommitMessage,
message.into_owned().trim(),
cargo_rustc_env,
);
}
}
if let Some(describe) = self.describe {
if let Ok(_value) = env::var(GIT_DESCRIBE_NAME) {
add_default_map_entry(
idempotent,
VergenKey::GitDescribe,
cargo_rustc_env,
cargo_warning,
);
} else {
let describe_refs = if describe.tags() {
SelectRef::AllTags
} else {
SelectRef::AnnotatedTags
};
let describe = match commit.describe().names(describe_refs).try_resolve()? {
Some(res) => {
if describe.dirty() {
let fmt = res.format_with_dirty_suffix(Some("dirty".to_string()))?;
fmt.to_string()
} else {
res.format()?.to_string()
}
}
_ => String::new(),
};
add_map_entry(VergenKey::GitDescribe, describe, cargo_rustc_env);
}
}
if let Some(dirty) = self.dirty {
if let Ok(_value) = env::var(GIT_DIRTY_NAME) {
add_default_map_entry(
idempotent,
VergenKey::GitDirty,
cargo_rustc_env,
cargo_warning,
);
} else {
let mut use_dirty = repo.is_dirty()?;
if !use_dirty && dirty.include_untracked() {
let index = repo.index_or_load_from_head_or_empty()?;
let patterns: [String; 0] = [];
let options = repo
.dirwalk_options()?
.emit_tracked(false)
.emit_empty_directories(false)
.emit_untracked(EmissionMode::Matching);
use_dirty |= repo
.dirwalk_iter(index, patterns, Default::default(), options)?
.any(|i| matches!(i, Ok(i) if i.entry.status == Status::Untracked));
}
add_map_entry(VergenKey::GitDirty, format!("{use_dirty}"), cargo_rustc_env);
}
}
if let Some(sha) = self.sha {
if let Ok(_value) = env::var(GIT_SHA_NAME) {
add_default_map_entry(
idempotent,
VergenKey::GitSha,
cargo_rustc_env,
cargo_warning,
);
} else {
let id = if sha.short() {
commit.short_id()?.to_string()
} else {
commit.id().to_string()
};
add_map_entry(VergenKey::GitSha, id, cargo_rustc_env);
}
}
self.cleanup();
Ok(())
}
fn get_commit<'a>(repo: &Repository, head: &mut Head<'a>) -> Result<Commit<'a>> {
Ok(if repo.is_shallow() {
let id = Self::get_id(head)?.ok_or_else(|| anyhow!("Not an Id"))?;
let object = id.try_object()?.ok_or_else(|| anyhow!("Not an Object"))?;
object.try_into_commit()?
} else {
head.peel_to_commit()?
})
}
fn get_id<'a>(head: &mut Head<'a>) -> Result<Option<Id<'a>>> {
head.try_peel_to_id().map_err(Into::into)
}
fn add_rerun_if_changed(
head: &Head<'_>,
git_path: &Path,
cargo_rerun_if_changed: &mut Vec<String>,
) {
let mut head_path = git_path.to_path_buf();
head_path.push("HEAD");
if head_path.exists() {
cargo_rerun_if_changed.push(format!("{}", head_path.display()));
}
if let head::Kind::Symbolic(reference) = &head.kind {
let mut ref_path = git_path.to_path_buf();
ref_path.push(reference.name.to_path());
if ref_path.exists() {
cargo_rerun_if_changed.push(format!("{}", ref_path.display()));
}
}
}
fn add_git_timestamp_entries(
&self,
idempotent: bool,
commit: &Commit<'_>,
cargo_rustc_env: &mut CargoRustcEnvMap,
cargo_warning: &mut CargoWarning,
) -> Result<()> {
let ts = self.compute_local_offset(commit)?;
if let Ok(_value) = env::var(GIT_COMMIT_DATE_NAME) {
add_default_map_entry(
idempotent,
VergenKey::GitCommitDate,
cargo_rustc_env,
cargo_warning,
);
} else {
self.add_git_date_entry(idempotent, &ts, cargo_rustc_env, cargo_warning)?;
}
if let Ok(_value) = env::var(GIT_COMMIT_TIMESTAMP_NAME) {
add_default_map_entry(
idempotent,
VergenKey::GitCommitTimestamp,
cargo_rustc_env,
cargo_warning,
);
} else {
self.add_git_timestamp_entry(idempotent, &ts, cargo_rustc_env, cargo_warning)?;
}
if let Ok(_value) = env::var(GIT_COMMIT_TIMESTAMP_UNIX_NAME) {
add_default_map_entry(
idempotent,
VergenKey::GitCommitTimestampUnix,
cargo_rustc_env,
cargo_warning,
);
} else {
self.add_git_timestamp_unix_entry(idempotent, &ts, cargo_rustc_env, cargo_warning);
}
Ok(())
}
fn add_git_timestamp_unix_entry(
&self,
idempotent: bool,
ts: &OffsetDateTime,
cargo_rustc_env: &mut CargoRustcEnvMap,
cargo_warning: &mut CargoWarning,
) {
if self.commit_timestamp_unix {
if idempotent {
add_default_map_entry(
idempotent,
VergenKey::GitCommitTimestampUnix,
cargo_rustc_env,
cargo_warning,
);
} else {
add_map_entry(
VergenKey::GitCommitTimestampUnix,
ts.unix_timestamp().to_string(),
cargo_rustc_env,
);
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn compute_local_offset(&self, commit: &Commit<'_>) -> Result<OffsetDateTime> {
let no_offset = OffsetDateTime::from_unix_timestamp(commit.time()?.seconds)?;
if self.use_local {
let local = UtcOffset::local_offset_at(no_offset)?;
let local_offset = no_offset.checked_to_offset(local).unwrap_or(no_offset);
Ok(local_offset)
} else {
Ok(no_offset)
}
}
fn add_git_date_entry(
&self,
idempotent: bool,
ts: &OffsetDateTime,
cargo_rustc_env: &mut CargoRustcEnvMap,
cargo_warning: &mut CargoWarning,
) -> Result<()> {
if self.commit_date {
if idempotent {
add_default_map_entry(
idempotent,
VergenKey::GitCommitDate,
cargo_rustc_env,
cargo_warning,
);
} else {
let format = format_description::parse("[year]-[month]-[day]")?;
add_map_entry(
VergenKey::GitCommitDate,
ts.format(&format)?,
cargo_rustc_env,
);
}
}
Ok(())
}
fn add_git_timestamp_entry(
&self,
idempotent: bool,
ts: &OffsetDateTime,
cargo_rustc_env: &mut CargoRustcEnvMap,
cargo_warning: &mut CargoWarning,
) -> Result<()> {
if self.commit_timestamp {
if idempotent {
add_default_map_entry(
idempotent,
VergenKey::GitCommitTimestamp,
cargo_rustc_env,
cargo_warning,
);
} else {
add_map_entry(
VergenKey::GitCommitTimestamp,
ts.format(&Iso8601::DEFAULT)?,
cargo_rustc_env,
);
}
}
Ok(())
}
#[cfg(feature = "vcs_info")]
fn vcs_fallback(&self) -> Option<(String, bool)> {
if self.vcs_info_fallback {
vergen_lib::vcs_info()
} else {
None
}
}
#[cfg(not(feature = "vcs_info"))]
#[allow(clippy::unused_self)]
fn vcs_fallback(&self) -> Option<(String, bool)> {
None
}
}
impl AddEntries for Gix {
fn add_map_entries(
&self,
idempotent: bool,
cargo_rustc_env: &mut CargoRustcEnvMap,
cargo_rerun_if_changed: &mut CargoRerunIfChanged,
cargo_warning: &mut CargoWarning,
) -> Result<()> {
self.add_entries(
idempotent,
cargo_rustc_env,
cargo_rerun_if_changed,
cargo_warning,
)
}
#[allow(clippy::too_many_lines)]
fn add_default_entries(
&self,
config: &DefaultConfig,
cargo_rustc_env_map: &mut CargoRustcEnvMap,
cargo_rerun_if_changed: &mut CargoRerunIfChanged,
cargo_warning: &mut CargoWarning,
) -> Result<()> {
if *config.fail_on_error() {
let error = Error::msg(format!("{}", config.error()));
Err(error)
} else {
cargo_warning.clear();
cargo_rerun_if_changed.clear();
cargo_warning.push(format!("{}", config.error()));
let vcs = self.vcs_fallback();
if self.branch {
add_default_map_entry(
*config.idempotent(),
VergenKey::GitBranch,
cargo_rustc_env_map,
cargo_warning,
);
}
if self.commit_author_email {
add_default_map_entry(
*config.idempotent(),
VergenKey::GitCommitAuthorEmail,
cargo_rustc_env_map,
cargo_warning,
);
}
if self.commit_author_name {
add_default_map_entry(
*config.idempotent(),
VergenKey::GitCommitAuthorName,
cargo_rustc_env_map,
cargo_warning,
);
}
if self.commit_count {
add_default_map_entry(
*config.idempotent(),
VergenKey::GitCommitCount,
cargo_rustc_env_map,
cargo_warning,
);
}
if self.commit_date {
add_default_map_entry(
*config.idempotent(),
VergenKey::GitCommitDate,
cargo_rustc_env_map,
cargo_warning,
);
}
if self.commit_message {
add_default_map_entry(
*config.idempotent(),
VergenKey::GitCommitMessage,
cargo_rustc_env_map,
cargo_warning,
);
}
if self.commit_timestamp {
add_default_map_entry(
*config.idempotent(),
VergenKey::GitCommitTimestamp,
cargo_rustc_env_map,
cargo_warning,
);
}
if self.commit_timestamp_unix {
add_default_map_entry(
*config.idempotent(),
VergenKey::GitCommitTimestampUnix,
cargo_rustc_env_map,
cargo_warning,
);
}
if self.describe.is_some() {
add_default_map_entry(
*config.idempotent(),
VergenKey::GitDescribe,
cargo_rustc_env_map,
cargo_warning,
);
}
if self.sha.is_some() {
if let Some((sha, _)) = &vcs {
add_map_entry(VergenKey::GitSha, sha.clone(), cargo_rustc_env_map);
} else {
add_default_map_entry(
*config.idempotent(),
VergenKey::GitSha,
cargo_rustc_env_map,
cargo_warning,
);
}
}
if self.dirty.is_some() {
if let Some((_, dirty)) = &vcs {
add_map_entry(VergenKey::GitDirty, dirty.to_string(), cargo_rustc_env_map);
} else {
add_default_map_entry(
*config.idempotent(),
VergenKey::GitDirty,
cargo_rustc_env_map,
cargo_warning,
);
}
}
Ok(())
}
}
}
#[cfg(test)]
mod test {
use super::Gix;
use anyhow::Result;
use serial_test::serial;
#[cfg(unix)]
use std::io::stdout;
use std::{env::temp_dir, io::Write};
#[cfg(unix)]
use test_util::TEST_MTIME;
use test_util::TestRepos;
use vergen::Emitter;
use vergen_lib::count_idempotent;
#[test]
#[serial]
#[allow(clippy::clone_on_copy, clippy::redundant_clone)]
fn gix_clone_works() -> Result<()> {
let gix = Gix::all_git();
let another = gix.clone();
assert_eq!(another, gix);
Ok(())
}
#[test]
#[serial]
fn gix_debug_works() -> Result<()> {
let gix = Gix::all_git();
let mut buf = vec![];
write!(buf, "{gix:?}")?;
assert!(!buf.is_empty());
Ok(())
}
#[test]
#[serial]
fn gix_default() -> Result<()> {
let gix = Gix::builder().build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(0, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_all_idempotent() -> Result<()> {
let gix = Gix::all_git();
let emitter = Emitter::default()
.idempotent()
.add_instructions(&gix)?
.test_emit();
assert_eq!(10, emitter.cargo_rustc_env_map().len());
assert_eq!(2, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(2, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_all_idempotent_no_warn() -> Result<()> {
let gix = Gix::all_git();
let emitter = Emitter::default()
.idempotent()
.quiet()
.add_instructions(&gix)?
.test_emit();
assert_eq!(10, emitter.cargo_rustc_env_map().len());
assert_eq!(2, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(2, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_all() -> Result<()> {
let gix = Gix::all_git();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(10, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_branch() -> Result<()> {
let gix = Gix::builder().branch(true).build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(1, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_commit_author_name() -> Result<()> {
let gix = Gix::builder().commit_author_name(true).build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(1, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_commit_author_email() -> Result<()> {
let gix = Gix::builder().commit_author_email(true).build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(1, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_commit_count() -> Result<()> {
let gix = Gix::builder().commit_count(true).build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(1, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_commit_message() -> Result<()> {
let gix = Gix::builder().commit_message(true).build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(1, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_commit_date() -> Result<()> {
let gix = Gix::builder().commit_date(true).build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(1, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[cfg(any(unix, target_os = "macos"))]
#[test]
#[serial]
fn git_commit_date_local() -> Result<()> {
let gix = Gix::builder().commit_date(true).use_local(true).build();
let emitter = Emitter::default()
.fail_on_error()
.add_instructions(&gix)?
.test_emit();
assert_eq!(1, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_commit_timestamp() -> Result<()> {
let gix = Gix::builder().commit_timestamp(true).build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(1, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_describe() -> Result<()> {
let gix = Gix::builder().describe(true, false, None).build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(1, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_sha() -> Result<()> {
let gix = Gix::builder().sha(false).build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(1, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_all_at_path() -> Result<()> {
let repo = TestRepos::new(false, false, false)?;
let mut gix = Gix::all_git();
let _ = gix.at_path(repo.path());
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(10, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_all_shallow_clone() -> Result<()> {
let repo = TestRepos::new(false, false, true)?;
let mut gix = Gix::all_git();
let _ = gix.at_path(repo.path());
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(10, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_error_fails() -> Result<()> {
let mut gix = Gix::all_git();
let _ = gix.at_path(temp_dir());
assert!(
Emitter::default()
.fail_on_error()
.add_instructions(&gix)
.is_err()
);
Ok(())
}
#[test]
#[serial]
fn git_error_defaults() -> Result<()> {
let mut gix = Gix::all_git();
let _ = gix.at_path(temp_dir());
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(0, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(11, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn git_error_idempotent() -> Result<()> {
let mut gix = Gix::all_git();
let _ = gix.at_path(temp_dir());
let emitter = Emitter::default()
.idempotent()
.add_instructions(&gix)?
.test_emit();
assert_eq!(10, emitter.cargo_rustc_env_map().len());
assert_eq!(10, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(11, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
fn source_date_epoch_does_not_affect_git() {
fn emit() -> Result<String> {
let mut stdout_buf = vec![];
let gix = Gix::builder()
.commit_date(true)
.commit_timestamp(true)
.build();
_ = Emitter::new()
.add_instructions(&gix)?
.emit_to(&mut stdout_buf)?;
Ok(String::from_utf8_lossy(&stdout_buf).into_owned())
}
let without = temp_env::with_var("SOURCE_DATE_EPOCH", None::<&str>, emit);
let with = temp_env::with_var("SOURCE_DATE_EPOCH", Some("1671809360"), emit);
assert_eq!(without.unwrap(), with.unwrap());
}
#[test]
#[serial]
#[cfg(unix)]
fn bad_source_date_epoch_ignored_by_git() {
use std::ffi::OsStr;
use std::os::unix::prelude::OsStrExt;
let source = [0x66, 0x6f, 0x80, 0x6f];
let os_str = OsStr::from_bytes(&source[..]);
temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
let result = || -> Result<()> {
let gix = Gix::builder().commit_date(true).build();
Emitter::new()
.idempotent()
.fail_on_error()
.add_instructions(&gix)?
.emit()
}();
assert!(result.is_ok());
});
}
#[test]
#[serial]
#[cfg(unix)]
fn bad_source_date_epoch_defaults() {
use std::ffi::OsStr;
use std::os::unix::prelude::OsStrExt;
let source = [0x66, 0x6f, 0x80, 0x6f];
let os_str = OsStr::from_bytes(&source[..]);
temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
let result = || -> Result<bool> {
let mut stdout_buf = vec![];
let gix = Gix::builder().commit_date(true).build();
Emitter::new()
.idempotent()
.add_instructions(&gix)?
.emit_to(&mut stdout_buf)
}();
assert!(result.is_ok());
});
}
#[test]
#[serial]
#[cfg(windows)]
fn bad_source_date_epoch_ignored_by_git() {
use std::ffi::OsString;
use std::os::windows::prelude::OsStringExt;
let source = [0x0066, 0x006f, 0xD800, 0x006f];
let os_string = OsString::from_wide(&source[..]);
let os_str = os_string.as_os_str();
temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
let result = || -> Result<bool> {
let mut stdout_buf = vec![];
let gix = Gix::builder().commit_date(true).build();
Emitter::default()
.fail_on_error()
.idempotent()
.add_instructions(&gix)?
.emit_to(&mut stdout_buf)
}();
assert!(result.is_ok());
});
}
#[test]
#[serial]
#[cfg(windows)]
fn bad_source_date_epoch_defaults() {
use std::ffi::OsString;
use std::os::windows::prelude::OsStringExt;
let source = [0x0066, 0x006f, 0xD800, 0x006f];
let os_string = OsString::from_wide(&source[..]);
let os_str = os_string.as_os_str();
temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
let result = || -> Result<bool> {
let mut stdout_buf = vec![];
let gix = Gix::builder().commit_date(true).build();
Emitter::default()
.idempotent()
.add_instructions(&gix)?
.emit_to(&mut stdout_buf)
}();
assert!(result.is_ok());
});
}
#[test]
#[serial]
#[cfg(unix)]
fn git_no_index_update() -> Result<()> {
let repo = TestRepos::new(true, true, false)?;
repo.set_index_magic_mtime()?;
let mut gix = Gix::builder().all().describe(true, true, None).build();
let _ = gix.at_path(repo.path());
let failed = Emitter::default()
.add_instructions(&gix)?
.emit_to(&mut stdout())?;
assert!(!failed);
assert_eq!(*TEST_MTIME, repo.get_index_magic_mtime()?);
Ok(())
}
#[test]
#[serial]
#[cfg(feature = "allow_remote")]
fn remote_clone_works() -> Result<()> {
let gix = Gix::all()
.force_remote(true)
.remote_url("https://github.com/rustyhorde/vergen-cl.git")
.describe(true, true, None)
.build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(10, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(1, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
#[cfg(feature = "allow_remote")]
fn remote_clone_with_path_works() -> Result<()> {
let remote_path = temp_dir().join("blah");
let gix = Gix::all()
.force_remote(true)
.remote_repo_path(&remote_path)
.remote_url("https://github.com/rustyhorde/vergen-cl.git")
.describe(true, true, None)
.build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(10, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(1, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
#[cfg(feature = "allow_remote")]
fn remote_clone_with_force_local_works() -> Result<()> {
let gix = Gix::all()
.force_local(true)
.force_remote(true)
.remote_url("https://github.com/rustyhorde/vergen-cl.git")
.describe(true, true, None)
.build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(10, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(0, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
#[cfg(feature = "allow_remote")]
fn remote_clone_with_tag_works() -> Result<()> {
let gix = Gix::all()
.force_remote(true)
.remote_tag("0.3.9")
.remote_url("https://github.com/rustyhorde/vergen-cl.git")
.describe(true, true, None)
.build();
let emitter = Emitter::default().add_instructions(&gix)?.test_emit();
assert_eq!(10, emitter.cargo_rustc_env_map().len());
assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
assert_eq!(1, emitter.cargo_warning().len());
Ok(())
}
#[test]
#[serial]
#[cfg(feature = "vcs_info")]
fn vcs_info_fallback_populates_sha_and_dirty() -> Result<()> {
let tmp = temp_dir().join(format!("vergen_gix_vcs_info_{}", std::process::id()));
std::fs::create_dir_all(&tmp)?;
let mut file = std::fs::File::create(tmp.join(".cargo_vcs_info.json"))?;
write!(
file,
r#"{{"git":{{"sha1":"deadbeef","dirty":true}},"path_in_vcs":""}}"#
)?;
let gix = Gix::builder()
.sha(false)
.dirty(false)
.vcs_info_fallback(true)
.local_repo_path(tmp.join("not-a-repo"))
.build();
let mut stdout_buf = vec![];
temp_env::with_var(
"CARGO_MANIFEST_DIR",
Some(tmp.as_os_str()),
|| -> Result<()> {
let mut emitter = Emitter::default();
_ = emitter.add_instructions(&gix)?.emit_to(&mut stdout_buf)?;
Ok(())
},
)?;
let output = String::from_utf8_lossy(&stdout_buf);
let _ = std::fs::remove_dir_all(&tmp).ok();
assert!(
output.contains("cargo:rustc-env=VERGEN_GIT_SHA=deadbeef"),
"{output}"
);
assert!(
output.contains("cargo:rustc-env=VERGEN_GIT_DIRTY=true"),
"{output}"
);
Ok(())
}
#[test]
#[serial]
fn commit_timestamp_unix_works() -> Result<()> {
let gix = Gix::builder().commit_timestamp_unix(true).build();
let mut stdout_buf = vec![];
_ = Emitter::default()
.add_instructions(&gix)?
.emit_to(&mut stdout_buf)?;
let output = String::from_utf8_lossy(&stdout_buf);
let line = output
.lines()
.find(|l| l.starts_with("cargo:rustc-env=VERGEN_GIT_COMMIT_TIMESTAMP_UNIX="))
.expect("unix commit timestamp emitted");
let value = line.trim_start_matches("cargo:rustc-env=VERGEN_GIT_COMMIT_TIMESTAMP_UNIX=");
assert!(value.parse::<i64>().is_ok(), "value: {value}");
Ok(())
}
#[test]
#[serial]
fn commit_timestamp_unix_idempotent() -> Result<()> {
let gix = Gix::builder().commit_timestamp_unix(true).build();
let emitter = Emitter::default()
.idempotent()
.add_instructions(&gix)?
.test_emit();
assert_eq!(1, emitter.cargo_rustc_env_map().len());
assert_eq!(1, count_idempotent(emitter.cargo_rustc_env_map()));
Ok(())
}
#[test]
#[serial]
fn commit_timestamp_unix_override_works() {
temp_env::with_var("VERGEN_GIT_COMMIT_TIMESTAMP_UNIX", Some("12345"), || {
let result = || -> Result<()> {
let gix = Gix::builder().commit_timestamp_unix(true).build();
let mut stdout_buf = vec![];
_ = Emitter::default()
.add_instructions(&gix)?
.emit_to(&mut stdout_buf)?;
let output = String::from_utf8_lossy(&stdout_buf);
assert!(
output.contains("cargo:rustc-env=VERGEN_GIT_COMMIT_TIMESTAMP_UNIX=12345"),
"{output}"
);
Ok(())
}();
assert!(result.is_ok());
});
}
}