1#[cfg(test)]
10use anyhow::anyhow;
11use anyhow::{Error, Result};
12use derive_builder::Builder as DeriveBuilder;
13use git2_rs::{
14 BranchType, Commit, DescribeFormatOptions, DescribeOptions, Reference, Repository,
15 StatusOptions,
16};
17use std::{
18 env::{self, VarError},
19 path::{Path, PathBuf},
20 str::FromStr,
21};
22use time::{
23 OffsetDateTime, UtcOffset,
24 format_description::{self, well_known::Iso8601},
25};
26use vergen_lib::{
27 AddEntries, CargoRerunIfChanged, CargoRustcEnvMap, CargoWarning, DefaultConfig, VergenKey,
28 add_default_map_entry, add_map_entry,
29 constants::{
30 GIT_BRANCH_NAME, GIT_COMMIT_AUTHOR_EMAIL, GIT_COMMIT_AUTHOR_NAME, GIT_COMMIT_COUNT,
31 GIT_COMMIT_DATE_NAME, GIT_COMMIT_MESSAGE, GIT_COMMIT_TIMESTAMP_NAME, GIT_DESCRIBE_NAME,
32 GIT_DIRTY_NAME, GIT_SHA_NAME,
33 },
34};
35
36#[derive(Clone, Debug, DeriveBuilder, PartialEq)]
84#[allow(clippy::struct_excessive_bools)]
85pub struct Git2 {
86 #[builder(default = "None")]
88 repo_path: Option<PathBuf>,
89 #[builder(default = "false")]
96 branch: bool,
97 #[builder(default = "false")]
104 commit_author_name: bool,
105 #[builder(default = "false")]
112 commit_author_email: bool,
113 #[builder(default = "false")]
119 commit_count: bool,
120 #[builder(default = "false")]
127 commit_message: bool,
128 #[builder(default = "false")]
135 commit_date: bool,
136 #[builder(default = "false")]
143 commit_timestamp: bool,
144 #[builder(default = "false", setter(custom))]
154 describe: bool,
155 #[builder(default = "false", private)]
157 describe_tags: bool,
158 #[builder(default = "false", private)]
160 describe_dirty: bool,
161 #[builder(default = "None", private)]
163 describe_match_pattern: Option<&'static str>,
164 #[builder(default = "false", setter(custom))]
174 sha: bool,
175 #[builder(default = "false", private)]
177 sha_short: bool,
178 #[builder(default = "false", setter(custom))]
186 dirty: bool,
187 #[builder(default = "false", private)]
189 dirty_include_untracked: bool,
190 #[builder(default = "false")]
192 use_local: bool,
193 #[cfg(test)]
194 #[builder(default = "false")]
196 fail: bool,
197}
198
199impl Git2Builder {
200 pub fn all_git() -> Result<Git2> {
206 Self::default()
207 .branch(true)
208 .commit_author_email(true)
209 .commit_author_name(true)
210 .commit_count(true)
211 .commit_date(true)
212 .commit_message(true)
213 .commit_timestamp(true)
214 .describe(false, false, None)
215 .sha(false)
216 .dirty(false)
217 .build()
218 .map_err(Into::into)
219 }
220
221 pub fn all(&mut self) -> &mut Self {
223 self.branch(true)
224 .commit_author_email(true)
225 .commit_author_name(true)
226 .commit_count(true)
227 .commit_date(true)
228 .commit_message(true)
229 .commit_timestamp(true)
230 .describe(false, false, None)
231 .sha(false)
232 .dirty(false)
233 }
234
235 pub fn describe(
245 &mut self,
246 tags: bool,
247 dirty: bool,
248 matches: Option<&'static str>,
249 ) -> &mut Self {
250 self.describe = Some(true);
251 let _ = self.describe_tags(tags);
252 let _ = self.describe_dirty(dirty);
253 let _ = self.describe_match_pattern(matches);
254 self
255 }
256
257 pub fn dirty(&mut self, include_untracked: bool) -> &mut Self {
265 self.dirty = Some(true);
266 let _ = self.dirty_include_untracked(include_untracked);
267 self
268 }
269
270 pub fn sha(&mut self, short: bool) -> &mut Self {
280 self.sha = Some(true);
281 let _ = self.sha_short(short);
282 self
283 }
284}
285
286impl Git2 {
287 fn any(&self) -> bool {
288 self.branch
289 || self.commit_author_email
290 || self.commit_author_name
291 || self.commit_count
292 || self.commit_date
293 || self.commit_message
294 || self.commit_timestamp
295 || self.describe
296 || self.sha
297 || self.dirty
298 }
299
300 pub fn at_path(&mut self, path: PathBuf) -> &mut Self {
302 self.repo_path = Some(path);
303 self
304 }
305
306 #[cfg(test)]
307 pub(crate) fn fail(&mut self) -> &mut Self {
308 self.fail = true;
309 self
310 }
311
312 #[cfg(not(test))]
313 fn add_entries(
314 &self,
315 idempotent: bool,
316 cargo_rustc_env: &mut CargoRustcEnvMap,
317 cargo_rerun_if_changed: &mut CargoRerunIfChanged,
318 cargo_warning: &mut CargoWarning,
319 ) -> Result<()> {
320 self.inner_add_entries(
321 idempotent,
322 cargo_rustc_env,
323 cargo_rerun_if_changed,
324 cargo_warning,
325 )
326 }
327
328 #[cfg(test)]
329 fn add_entries(
330 &self,
331 idempotent: bool,
332 cargo_rustc_env: &mut CargoRustcEnvMap,
333 cargo_rerun_if_changed: &mut CargoRerunIfChanged,
334 cargo_warning: &mut CargoWarning,
335 ) -> Result<()> {
336 if self.fail {
337 return Err(anyhow!("failed to create entries"));
338 }
339 self.inner_add_entries(
340 idempotent,
341 cargo_rustc_env,
342 cargo_rerun_if_changed,
343 cargo_warning,
344 )
345 }
346
347 #[allow(clippy::too_many_lines)]
348 fn inner_add_entries(
349 &self,
350 idempotent: bool,
351 cargo_rustc_env: &mut CargoRustcEnvMap,
352 cargo_rerun_if_changed: &mut CargoRerunIfChanged,
353 cargo_warning: &mut CargoWarning,
354 ) -> Result<()> {
355 let repo_dir = if let Some(path) = &self.repo_path {
356 path.clone()
357 } else {
358 env::current_dir()?
359 };
360 let repo = Repository::discover(repo_dir)?;
361 let ref_head = repo.find_reference("HEAD")?;
362 let git_path = repo.path().to_path_buf();
363 let commit = ref_head.peel_to_commit()?;
364
365 if !idempotent && self.any() {
366 Self::add_rerun_if_changed(&ref_head, &git_path, cargo_rerun_if_changed);
367 }
368
369 if self.branch {
370 if let Ok(_value) = env::var(GIT_BRANCH_NAME) {
371 add_default_map_entry(VergenKey::GitBranch, cargo_rustc_env, cargo_warning);
372 } else {
373 Self::add_branch_name(false, &repo, cargo_rustc_env, cargo_warning)?;
374 }
375 }
376
377 if self.commit_author_email {
378 if let Ok(_value) = env::var(GIT_COMMIT_AUTHOR_EMAIL) {
379 add_default_map_entry(
380 VergenKey::GitCommitAuthorEmail,
381 cargo_rustc_env,
382 cargo_warning,
383 );
384 } else {
385 Self::add_opt_value(
386 commit.author().email(),
387 VergenKey::GitCommitAuthorEmail,
388 cargo_rustc_env,
389 cargo_warning,
390 );
391 }
392 }
393
394 if self.commit_author_name {
395 if let Ok(_value) = env::var(GIT_COMMIT_AUTHOR_NAME) {
396 add_default_map_entry(
397 VergenKey::GitCommitAuthorName,
398 cargo_rustc_env,
399 cargo_warning,
400 );
401 } else {
402 Self::add_opt_value(
403 commit.author().name(),
404 VergenKey::GitCommitAuthorName,
405 cargo_rustc_env,
406 cargo_warning,
407 );
408 }
409 }
410
411 if self.commit_count {
412 if let Ok(_value) = env::var(GIT_COMMIT_COUNT) {
413 add_default_map_entry(VergenKey::GitCommitCount, cargo_rustc_env, cargo_warning);
414 } else {
415 Self::add_commit_count(false, &repo, cargo_rustc_env, cargo_warning);
416 }
417 }
418
419 self.add_git_timestamp_entries(&commit, idempotent, cargo_rustc_env, cargo_warning)?;
420
421 if self.commit_message {
422 if let Ok(_value) = env::var(GIT_COMMIT_MESSAGE) {
423 add_default_map_entry(VergenKey::GitCommitMessage, cargo_rustc_env, cargo_warning);
424 } else {
425 Self::add_opt_value(
426 commit.message(),
427 VergenKey::GitCommitMessage,
428 cargo_rustc_env,
429 cargo_warning,
430 );
431 }
432 }
433
434 if self.sha {
435 if let Ok(_value) = env::var(GIT_SHA_NAME) {
436 add_default_map_entry(VergenKey::GitSha, cargo_rustc_env, cargo_warning);
437 } else if self.sha_short {
438 let obj = repo.revparse_single("HEAD")?;
439 Self::add_opt_value(
440 obj.short_id()?.as_str(),
441 VergenKey::GitSha,
442 cargo_rustc_env,
443 cargo_warning,
444 );
445 } else {
446 add_map_entry(VergenKey::GitSha, commit.id().to_string(), cargo_rustc_env);
447 }
448 }
449
450 if self.dirty {
451 if let Ok(_value) = env::var(GIT_DIRTY_NAME) {
452 add_default_map_entry(VergenKey::GitDirty, cargo_rustc_env, cargo_warning);
453 } else {
454 let mut status_options = StatusOptions::new();
455
456 _ = status_options.include_untracked(self.dirty_include_untracked);
457 let statuses = repo.statuses(Some(&mut status_options))?;
458
459 let n_dirty = statuses
460 .iter()
461 .filter(|each_status| !each_status.status().is_ignored())
462 .count();
463
464 add_map_entry(
465 VergenKey::GitDirty,
466 format!("{}", n_dirty > 0),
467 cargo_rustc_env,
468 );
469 }
470 }
471
472 if self.describe {
473 if let Ok(_value) = env::var(GIT_DESCRIBE_NAME) {
474 add_default_map_entry(VergenKey::GitDescribe, cargo_rustc_env, cargo_warning);
475 } else {
476 let mut describe_opts = DescribeOptions::new();
477 let mut format_opts = DescribeFormatOptions::new();
478
479 _ = describe_opts.show_commit_oid_as_fallback(true);
480
481 if self.describe_dirty {
482 _ = format_opts.dirty_suffix("-dirty");
483 }
484
485 if self.describe_tags {
486 _ = describe_opts.describe_tags();
487 }
488
489 if let Some(pattern) = self.describe_match_pattern {
490 _ = describe_opts.pattern(pattern);
491 }
492
493 let describe = repo
494 .describe(&describe_opts)
495 .map(|x| x.format(Some(&format_opts)).map_err(Error::from))??;
496 add_map_entry(VergenKey::GitDescribe, describe, cargo_rustc_env);
497 }
498 }
499
500 Ok(())
501 }
502
503 fn add_rerun_if_changed(
504 ref_head: &Reference<'_>,
505 git_path: &Path,
506 cargo_rerun_if_changed: &mut CargoRerunIfChanged,
507 ) {
508 let mut head_path = git_path.to_path_buf();
510 head_path.push("HEAD");
511
512 if head_path.exists() {
514 cargo_rerun_if_changed.push(format!("{}", head_path.display()));
515 }
516
517 if let Ok(resolved) = ref_head.resolve()
518 && let Some(name) = resolved.name()
519 {
520 let ref_path = git_path.to_path_buf();
521 let path = ref_path.join(name);
522 if path.exists() {
524 cargo_rerun_if_changed.push(format!("{}", path.display()));
525 }
526 }
527 }
528
529 fn add_branch_name(
530 add_default: bool,
531 repo: &Repository,
532 cargo_rustc_env: &mut CargoRustcEnvMap,
533 cargo_warning: &mut CargoWarning,
534 ) -> Result<()> {
535 if repo.head_detached()? {
536 if add_default {
537 add_default_map_entry(VergenKey::GitBranch, cargo_rustc_env, cargo_warning);
538 } else {
539 add_map_entry(VergenKey::GitBranch, "HEAD", cargo_rustc_env);
540 }
541 } else {
542 let locals = repo.branches(Some(BranchType::Local))?;
543 let mut found_head = false;
544 for (local, _bt) in locals.filter_map(std::result::Result::ok) {
545 if local.is_head()
546 && let Some(name) = local.name()?
547 {
548 add_map_entry(VergenKey::GitBranch, name, cargo_rustc_env);
549 found_head = !add_default;
550 break;
551 }
552 }
553 if !found_head {
554 add_default_map_entry(VergenKey::GitBranch, cargo_rustc_env, cargo_warning);
555 }
556 }
557 Ok(())
558 }
559
560 #[allow(clippy::map_unwrap_or)]
561 fn add_opt_value(
562 value: Option<&str>,
563 key: VergenKey,
564 cargo_rustc_env: &mut CargoRustcEnvMap,
565 cargo_warning: &mut CargoWarning,
566 ) {
567 value
568 .map(|val| add_map_entry(key, val, cargo_rustc_env))
569 .unwrap_or_else(|| add_default_map_entry(key, cargo_rustc_env, cargo_warning));
570 }
571
572 fn add_commit_count(
573 add_default: bool,
574 repo: &Repository,
575 cargo_rustc_env: &mut CargoRustcEnvMap,
576 cargo_warning: &mut CargoWarning,
577 ) {
578 let key = VergenKey::GitCommitCount;
579 if !add_default
580 && let Ok(mut revwalk) = repo.revwalk()
581 && revwalk.push_head().is_ok()
582 {
583 add_map_entry(key, revwalk.count().to_string(), cargo_rustc_env);
584 return;
585 }
586 add_default_map_entry(key, cargo_rustc_env, cargo_warning);
587 }
588
589 fn add_git_timestamp_entries(
590 &self,
591 commit: &Commit<'_>,
592 idempotent: bool,
593 cargo_rustc_env: &mut CargoRustcEnvMap,
594 cargo_warning: &mut CargoWarning,
595 ) -> Result<()> {
596 let (sde, ts) = match env::var("SOURCE_DATE_EPOCH") {
597 Ok(v) => (
598 true,
599 OffsetDateTime::from_unix_timestamp(i64::from_str(&v)?)?,
600 ),
601 Err(VarError::NotPresent) => self.compute_local_offset(commit)?,
602 Err(e) => return Err(e.into()),
603 };
604
605 if let Ok(_value) = env::var(GIT_COMMIT_DATE_NAME) {
606 add_default_map_entry(VergenKey::GitCommitDate, cargo_rustc_env, cargo_warning);
607 } else {
608 self.add_git_date_entry(idempotent, sde, &ts, cargo_rustc_env, cargo_warning)?;
609 }
610 if let Ok(_value) = env::var(GIT_COMMIT_TIMESTAMP_NAME) {
611 add_default_map_entry(
612 VergenKey::GitCommitTimestamp,
613 cargo_rustc_env,
614 cargo_warning,
615 );
616 } else {
617 self.add_git_timestamp_entry(idempotent, sde, &ts, cargo_rustc_env, cargo_warning)?;
618 }
619 Ok(())
620 }
621
622 #[cfg_attr(coverage_nightly, coverage(off))]
623 fn compute_local_offset(&self, commit: &Commit<'_>) -> Result<(bool, OffsetDateTime)> {
625 let no_offset = OffsetDateTime::from_unix_timestamp(commit.time().seconds())?;
626 if self.use_local {
627 let local = UtcOffset::local_offset_at(no_offset)?;
628 let local_offset = no_offset.checked_to_offset(local).unwrap_or(no_offset);
629 Ok((false, local_offset))
630 } else {
631 Ok((false, no_offset))
632 }
633 }
634
635 fn add_git_date_entry(
636 &self,
637 idempotent: bool,
638 source_date_epoch: bool,
639 ts: &OffsetDateTime,
640 cargo_rustc_env: &mut CargoRustcEnvMap,
641 cargo_warning: &mut CargoWarning,
642 ) -> Result<()> {
643 if self.commit_date {
644 if idempotent && !source_date_epoch {
645 add_default_map_entry(VergenKey::GitCommitDate, cargo_rustc_env, cargo_warning);
646 } else {
647 let format = format_description::parse("[year]-[month]-[day]")?;
648 add_map_entry(
649 VergenKey::GitCommitDate,
650 ts.format(&format)?,
651 cargo_rustc_env,
652 );
653 }
654 }
655 Ok(())
656 }
657
658 fn add_git_timestamp_entry(
659 &self,
660 idempotent: bool,
661 source_date_epoch: bool,
662 ts: &OffsetDateTime,
663 cargo_rustc_env: &mut CargoRustcEnvMap,
664 cargo_warning: &mut CargoWarning,
665 ) -> Result<()> {
666 if self.commit_timestamp {
667 if idempotent && !source_date_epoch {
668 add_default_map_entry(
669 VergenKey::GitCommitTimestamp,
670 cargo_rustc_env,
671 cargo_warning,
672 );
673 } else {
674 add_map_entry(
675 VergenKey::GitCommitTimestamp,
676 ts.format(&Iso8601::DEFAULT)?,
677 cargo_rustc_env,
678 );
679 }
680 }
681 Ok(())
682 }
683}
684
685impl AddEntries for Git2 {
686 fn add_map_entries(
687 &self,
688 idempotent: bool,
689 cargo_rustc_env: &mut CargoRustcEnvMap,
690 cargo_rerun_if_changed: &mut CargoRerunIfChanged,
691 cargo_warning: &mut CargoWarning,
692 ) -> Result<()> {
693 if self.any() {
694 self.add_entries(
695 idempotent,
696 cargo_rustc_env,
697 cargo_rerun_if_changed,
698 cargo_warning,
699 )?;
700 }
701 Ok(())
702 }
703
704 fn add_default_entries(
705 &self,
706 config: &DefaultConfig,
707 cargo_rustc_env_map: &mut CargoRustcEnvMap,
708 cargo_rerun_if_changed: &mut CargoRerunIfChanged,
709 cargo_warning: &mut CargoWarning,
710 ) -> Result<()> {
711 if *config.fail_on_error() {
712 let error = Error::msg(format!("{}", config.error()));
713 Err(error)
714 } else {
715 cargo_warning.clear();
717 cargo_rerun_if_changed.clear();
718
719 cargo_warning.push(format!("{}", config.error()));
720
721 if self.branch {
722 add_default_map_entry(VergenKey::GitBranch, cargo_rustc_env_map, cargo_warning);
723 }
724 if self.commit_author_email {
725 add_default_map_entry(
726 VergenKey::GitCommitAuthorEmail,
727 cargo_rustc_env_map,
728 cargo_warning,
729 );
730 }
731 if self.commit_author_name {
732 add_default_map_entry(
733 VergenKey::GitCommitAuthorName,
734 cargo_rustc_env_map,
735 cargo_warning,
736 );
737 }
738 if self.commit_count {
739 add_default_map_entry(
740 VergenKey::GitCommitCount,
741 cargo_rustc_env_map,
742 cargo_warning,
743 );
744 }
745 if self.commit_date {
746 add_default_map_entry(VergenKey::GitCommitDate, cargo_rustc_env_map, cargo_warning);
747 }
748 if self.commit_message {
749 add_default_map_entry(
750 VergenKey::GitCommitMessage,
751 cargo_rustc_env_map,
752 cargo_warning,
753 );
754 }
755 if self.commit_timestamp {
756 add_default_map_entry(
757 VergenKey::GitCommitTimestamp,
758 cargo_rustc_env_map,
759 cargo_warning,
760 );
761 }
762 if self.describe {
763 add_default_map_entry(VergenKey::GitDescribe, cargo_rustc_env_map, cargo_warning);
764 }
765 if self.sha {
766 add_default_map_entry(VergenKey::GitSha, cargo_rustc_env_map, cargo_warning);
767 }
768 if self.dirty {
769 add_default_map_entry(VergenKey::GitDirty, cargo_rustc_env_map, cargo_warning);
770 }
771 Ok(())
772 }
773 }
774}
775
776#[cfg(test)]
777mod test {
778 use super::{Git2, Git2Builder};
779 use anyhow::Result;
780 use git2_rs::Repository;
781 use serial_test::serial;
782 #[cfg(unix)]
783 use std::io::stdout;
784 use std::{collections::BTreeMap, env::current_dir, io::Write};
785 #[cfg(unix)]
786 use test_util::TEST_MTIME;
787 use test_util::TestRepos;
788 use vergen::Emitter;
789 use vergen_lib::{VergenKey, count_idempotent};
790
791 #[test]
792 #[serial]
793 #[allow(clippy::clone_on_copy, clippy::redundant_clone)]
794 fn git2_clone_works() -> Result<()> {
795 let git2 = Git2Builder::all_git()?;
796 let another = git2.clone();
797 assert_eq!(another, git2);
798 Ok(())
799 }
800
801 #[test]
802 #[serial]
803 fn git2_debug_works() -> Result<()> {
804 let git2 = Git2Builder::all_git()?;
805 let mut buf = vec![];
806 write!(buf, "{git2:?}")?;
807 assert!(!buf.is_empty());
808 Ok(())
809 }
810
811 #[test]
812 #[serial]
813 fn git2_default() -> Result<()> {
814 let git2 = Git2Builder::default().build()?;
815 let emitter = Emitter::default().add_instructions(&git2)?.test_emit();
816 assert_eq!(0, emitter.cargo_rustc_env_map().len());
817 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
818 assert_eq!(0, emitter.cargo_warning().len());
819 Ok(())
820 }
821
822 #[test]
823 #[serial]
824 fn empty_email_is_default() -> Result<()> {
825 let mut cargo_rustc_env = BTreeMap::new();
826 let mut cargo_warning = vec![];
827 Git2::add_opt_value(
828 None,
829 VergenKey::GitCommitAuthorEmail,
830 &mut cargo_rustc_env,
831 &mut cargo_warning,
832 );
833 assert_eq!(1, cargo_rustc_env.len());
834 assert_eq!(1, cargo_warning.len());
835 Ok(())
836 }
837
838 #[test]
839 #[serial]
840 fn bad_revwalk_is_default() -> Result<()> {
841 let mut cargo_rustc_env = BTreeMap::new();
842 let mut cargo_warning = vec![];
843 let repo = Repository::discover(current_dir()?)?;
844 Git2::add_commit_count(true, &repo, &mut cargo_rustc_env, &mut cargo_warning);
845 assert_eq!(1, cargo_rustc_env.len());
846 assert_eq!(1, cargo_warning.len());
847 Ok(())
848 }
849
850 #[test]
851 #[serial]
852 fn head_not_found_is_default() -> Result<()> {
853 let test_repo = TestRepos::new(false, false, false)?;
854 let mut map = BTreeMap::new();
855 let mut cargo_warning = vec![];
856 let repo = Repository::discover(current_dir()?)?;
857 Git2::add_branch_name(true, &repo, &mut map, &mut cargo_warning)?;
858 assert_eq!(1, map.len());
859 assert_eq!(1, cargo_warning.len());
860 let mut map = BTreeMap::new();
861 let mut cargo_warning = vec![];
862 let repo = Repository::discover(test_repo.path())?;
863 Git2::add_branch_name(true, &repo, &mut map, &mut cargo_warning)?;
864 assert_eq!(1, map.len());
865 assert_eq!(1, cargo_warning.len());
866 Ok(())
867 }
868
869 #[test]
870 #[serial]
871 fn git_all_idempotent() -> Result<()> {
872 let git2 = Git2Builder::all_git()?;
873 let emitter = Emitter::default()
874 .idempotent()
875 .add_instructions(&git2)?
876 .test_emit();
877 assert_eq!(10, emitter.cargo_rustc_env_map().len());
878 assert_eq!(2, count_idempotent(emitter.cargo_rustc_env_map()));
879 assert_eq!(2, emitter.cargo_warning().len());
880 Ok(())
881 }
882
883 #[test]
884 #[serial]
885 fn git_all_shallow_clone() -> Result<()> {
886 let repo = TestRepos::new(false, false, true)?;
887 let mut git2 = Git2Builder::all_git()?;
888 let _ = git2.at_path(repo.path());
889 let emitter = Emitter::default().add_instructions(&git2)?.test_emit();
890 assert_eq!(10, emitter.cargo_rustc_env_map().len());
891 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
892 assert_eq!(0, emitter.cargo_warning().len());
893 Ok(())
894 }
895
896 #[test]
897 #[serial]
898 fn git_all_idempotent_no_warn() -> Result<()> {
899 let git2 = Git2Builder::all_git()?;
900 let emitter = Emitter::default()
901 .idempotent()
902 .quiet()
903 .add_instructions(&git2)?
904 .test_emit();
905
906 assert_eq!(10, emitter.cargo_rustc_env_map().len());
907 assert_eq!(2, count_idempotent(emitter.cargo_rustc_env_map()));
908 assert_eq!(2, emitter.cargo_warning().len());
909 Ok(())
910 }
911
912 #[test]
913 #[serial]
914 fn git_all() -> Result<()> {
915 let git2 = Git2Builder::all_git()?;
916 let emitter = Emitter::default().add_instructions(&git2)?.test_emit();
917 assert_eq!(10, emitter.cargo_rustc_env_map().len());
918 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
919 assert_eq!(0, emitter.cargo_warning().len());
920 Ok(())
921 }
922
923 #[test]
924 #[serial]
925 fn git_error_fails() -> Result<()> {
926 let mut git2 = Git2Builder::all_git()?;
927 let _ = git2.fail();
928 assert!(
929 Emitter::default()
930 .fail_on_error()
931 .add_instructions(&git2)
932 .is_err()
933 );
934 Ok(())
935 }
936
937 #[test]
938 #[serial]
939 fn git_error_defaults() -> Result<()> {
940 let mut git2 = Git2Builder::all_git()?;
941 let _ = git2.fail();
942 let emitter = Emitter::default().add_instructions(&git2)?.test_emit();
943 assert_eq!(10, emitter.cargo_rustc_env_map().len());
944 assert_eq!(10, count_idempotent(emitter.cargo_rustc_env_map()));
945 assert_eq!(11, emitter.cargo_warning().len());
946 Ok(())
947 }
948
949 #[test]
950 #[serial]
951 fn source_date_epoch_works() {
952 temp_env::with_var("SOURCE_DATE_EPOCH", Some("1671809360"), || {
953 let result = || -> Result<()> {
954 let mut stdout_buf = vec![];
955 let gix = Git2Builder::default()
956 .commit_date(true)
957 .commit_timestamp(true)
958 .build()?;
959 _ = Emitter::new()
960 .idempotent()
961 .add_instructions(&gix)?
962 .emit_to(&mut stdout_buf)?;
963 let output = String::from_utf8_lossy(&stdout_buf);
964 for (idx, line) in output.lines().enumerate() {
965 if idx == 0 {
966 assert_eq!("cargo:rustc-env=VERGEN_GIT_COMMIT_DATE=2022-12-23", line);
967 } else if idx == 1 {
968 assert_eq!(
969 "cargo:rustc-env=VERGEN_GIT_COMMIT_TIMESTAMP=2022-12-23T15:29:20.000000000Z",
970 line
971 );
972 }
973 }
974 Ok(())
975 }();
976 assert!(result.is_ok());
977 });
978 }
979
980 #[test]
981 #[serial]
982 #[cfg(unix)]
983 fn bad_source_date_epoch_fails() {
984 use std::ffi::OsStr;
985 use std::os::unix::prelude::OsStrExt;
986
987 let source = [0x66, 0x6f, 0x80, 0x6f];
988 let os_str = OsStr::from_bytes(&source[..]);
989 temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
990 let result = || -> Result<bool> {
991 let mut stdout_buf = vec![];
992 let gix = Git2Builder::default().commit_date(true).build()?;
993 Emitter::new()
994 .idempotent()
995 .fail_on_error()
996 .add_instructions(&gix)?
997 .emit_to(&mut stdout_buf)
998 }();
999 assert!(result.is_err());
1000 });
1001 }
1002
1003 #[test]
1004 #[serial]
1005 #[cfg(unix)]
1006 fn bad_source_date_epoch_defaults() {
1007 use std::ffi::OsStr;
1008 use std::os::unix::prelude::OsStrExt;
1009
1010 let source = [0x66, 0x6f, 0x80, 0x6f];
1011 let os_str = OsStr::from_bytes(&source[..]);
1012 temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
1013 let result = || -> Result<bool> {
1014 let mut stdout_buf = vec![];
1015 let gix = Git2Builder::default().commit_date(true).build()?;
1016 Emitter::new()
1017 .idempotent()
1018 .add_instructions(&gix)?
1019 .emit_to(&mut stdout_buf)
1020 }();
1021 assert!(result.is_ok());
1022 });
1023 }
1024
1025 #[test]
1026 #[serial]
1027 #[cfg(windows)]
1028 fn bad_source_date_epoch_fails() {
1029 use std::ffi::OsString;
1030 use std::os::windows::prelude::OsStringExt;
1031
1032 let source = [0x0066, 0x006f, 0xD800, 0x006f];
1033 let os_string = OsString::from_wide(&source[..]);
1034 let os_str = os_string.as_os_str();
1035 temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
1036 let result = || -> Result<bool> {
1037 let mut stdout_buf = vec![];
1038 let gix = Git2Builder::default().commit_date(true).build()?;
1039 Emitter::new()
1040 .fail_on_error()
1041 .idempotent()
1042 .add_instructions(&gix)?
1043 .emit_to(&mut stdout_buf)
1044 }();
1045 assert!(result.is_err());
1046 });
1047 }
1048
1049 #[test]
1050 #[serial]
1051 #[cfg(windows)]
1052 fn bad_source_date_epoch_defaults() {
1053 use std::ffi::OsString;
1054 use std::os::windows::prelude::OsStringExt;
1055
1056 let source = [0x0066, 0x006f, 0xD800, 0x006f];
1057 let os_string = OsString::from_wide(&source[..]);
1058 let os_str = os_string.as_os_str();
1059 temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
1060 let result = || -> Result<bool> {
1061 let mut stdout_buf = vec![];
1062 let gix = Git2Builder::default().commit_date(true).build()?;
1063 Emitter::new()
1064 .idempotent()
1065 .add_instructions(&gix)?
1066 .emit_to(&mut stdout_buf)
1067 }();
1068 assert!(result.is_ok());
1069 });
1070 }
1071
1072 #[test]
1073 #[serial]
1074 #[cfg(unix)]
1075 fn git_no_index_update() -> Result<()> {
1076 let repo = TestRepos::new(true, true, false)?;
1077 repo.set_index_magic_mtime()?;
1078
1079 let mut git2 = Git2Builder::default()
1080 .all()
1081 .describe(true, true, None)
1082 .build()?;
1083 let _ = git2.at_path(repo.path());
1084 let failed = Emitter::default()
1085 .add_instructions(&git2)?
1086 .emit_to(&mut stdout())?;
1087 assert!(!failed);
1088
1089 assert_eq!(*TEST_MTIME, repo.get_index_magic_mtime()?);
1090 Ok(())
1091 }
1092}