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 format_description::{self, well_known::Iso8601},
24 OffsetDateTime, UtcOffset,
25};
26use vergen_lib::{
27 add_default_map_entry, add_map_entry,
28 constants::{
29 GIT_BRANCH_NAME, GIT_COMMIT_AUTHOR_EMAIL, GIT_COMMIT_AUTHOR_NAME, GIT_COMMIT_COUNT,
30 GIT_COMMIT_DATE_NAME, GIT_COMMIT_MESSAGE, GIT_COMMIT_TIMESTAMP_NAME, GIT_DESCRIBE_NAME,
31 GIT_DIRTY_NAME, GIT_SHA_NAME,
32 },
33 AddEntries, CargoRerunIfChanged, CargoRustcEnvMap, CargoWarning, DefaultConfig, VergenKey,
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 if let Some(name) = resolved.name() {
519 let ref_path = git_path.to_path_buf();
520 let path = ref_path.join(name);
521 if path.exists() {
523 cargo_rerun_if_changed.push(format!("{}", path.display()));
524 }
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 if let Some(name) = local.name()? {
547 add_map_entry(VergenKey::GitBranch, name, cargo_rustc_env);
548 found_head = !add_default;
549 break;
550 }
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 if let Ok(mut revwalk) = repo.revwalk() {
581 if revwalk.push_head().is_ok() {
582 add_map_entry(key, revwalk.count().to_string(), cargo_rustc_env);
583 return;
584 }
585 }
586 }
587 add_default_map_entry(key, cargo_rustc_env, cargo_warning);
588 }
589
590 fn add_git_timestamp_entries(
591 &self,
592 commit: &Commit<'_>,
593 idempotent: bool,
594 cargo_rustc_env: &mut CargoRustcEnvMap,
595 cargo_warning: &mut CargoWarning,
596 ) -> Result<()> {
597 let (sde, ts) = match env::var("SOURCE_DATE_EPOCH") {
598 Ok(v) => (
599 true,
600 OffsetDateTime::from_unix_timestamp(i64::from_str(&v)?)?,
601 ),
602 Err(VarError::NotPresent) => self.compute_local_offset(commit)?,
603 Err(e) => return Err(e.into()),
604 };
605
606 if let Ok(_value) = env::var(GIT_COMMIT_DATE_NAME) {
607 add_default_map_entry(VergenKey::GitCommitDate, cargo_rustc_env, cargo_warning);
608 } else {
609 self.add_git_date_entry(idempotent, sde, &ts, cargo_rustc_env, cargo_warning)?;
610 }
611 if let Ok(_value) = env::var(GIT_COMMIT_TIMESTAMP_NAME) {
612 add_default_map_entry(
613 VergenKey::GitCommitTimestamp,
614 cargo_rustc_env,
615 cargo_warning,
616 );
617 } else {
618 self.add_git_timestamp_entry(idempotent, sde, &ts, cargo_rustc_env, cargo_warning)?;
619 }
620 Ok(())
621 }
622
623 #[cfg_attr(coverage_nightly, coverage(off))]
624 fn compute_local_offset(&self, commit: &Commit<'_>) -> Result<(bool, OffsetDateTime)> {
626 let no_offset = OffsetDateTime::from_unix_timestamp(commit.time().seconds())?;
627 if self.use_local {
628 let local = UtcOffset::local_offset_at(no_offset)?;
629 let local_offset = no_offset.checked_to_offset(local).unwrap_or(no_offset);
630 Ok((false, local_offset))
631 } else {
632 Ok((false, no_offset))
633 }
634 }
635
636 fn add_git_date_entry(
637 &self,
638 idempotent: bool,
639 source_date_epoch: bool,
640 ts: &OffsetDateTime,
641 cargo_rustc_env: &mut CargoRustcEnvMap,
642 cargo_warning: &mut CargoWarning,
643 ) -> Result<()> {
644 if self.commit_date {
645 if idempotent && !source_date_epoch {
646 add_default_map_entry(VergenKey::GitCommitDate, cargo_rustc_env, cargo_warning);
647 } else {
648 let format = format_description::parse("[year]-[month]-[day]")?;
649 add_map_entry(
650 VergenKey::GitCommitDate,
651 ts.format(&format)?,
652 cargo_rustc_env,
653 );
654 }
655 }
656 Ok(())
657 }
658
659 fn add_git_timestamp_entry(
660 &self,
661 idempotent: bool,
662 source_date_epoch: bool,
663 ts: &OffsetDateTime,
664 cargo_rustc_env: &mut CargoRustcEnvMap,
665 cargo_warning: &mut CargoWarning,
666 ) -> Result<()> {
667 if self.commit_timestamp {
668 if idempotent && !source_date_epoch {
669 add_default_map_entry(
670 VergenKey::GitCommitTimestamp,
671 cargo_rustc_env,
672 cargo_warning,
673 );
674 } else {
675 add_map_entry(
676 VergenKey::GitCommitTimestamp,
677 ts.format(&Iso8601::DEFAULT)?,
678 cargo_rustc_env,
679 );
680 }
681 }
682 Ok(())
683 }
684}
685
686impl AddEntries for Git2 {
687 fn add_map_entries(
688 &self,
689 idempotent: bool,
690 cargo_rustc_env: &mut CargoRustcEnvMap,
691 cargo_rerun_if_changed: &mut CargoRerunIfChanged,
692 cargo_warning: &mut CargoWarning,
693 ) -> Result<()> {
694 if self.any() {
695 self.add_entries(
696 idempotent,
697 cargo_rustc_env,
698 cargo_rerun_if_changed,
699 cargo_warning,
700 )?;
701 }
702 Ok(())
703 }
704
705 fn add_default_entries(
706 &self,
707 config: &DefaultConfig,
708 cargo_rustc_env_map: &mut CargoRustcEnvMap,
709 cargo_rerun_if_changed: &mut CargoRerunIfChanged,
710 cargo_warning: &mut CargoWarning,
711 ) -> Result<()> {
712 if *config.fail_on_error() {
713 let error = Error::msg(format!("{}", config.error()));
714 Err(error)
715 } else {
716 cargo_warning.clear();
718 cargo_rerun_if_changed.clear();
719
720 cargo_warning.push(format!("{}", config.error()));
721
722 if self.branch {
723 add_default_map_entry(VergenKey::GitBranch, cargo_rustc_env_map, cargo_warning);
724 }
725 if self.commit_author_email {
726 add_default_map_entry(
727 VergenKey::GitCommitAuthorEmail,
728 cargo_rustc_env_map,
729 cargo_warning,
730 );
731 }
732 if self.commit_author_name {
733 add_default_map_entry(
734 VergenKey::GitCommitAuthorName,
735 cargo_rustc_env_map,
736 cargo_warning,
737 );
738 }
739 if self.commit_count {
740 add_default_map_entry(
741 VergenKey::GitCommitCount,
742 cargo_rustc_env_map,
743 cargo_warning,
744 );
745 }
746 if self.commit_date {
747 add_default_map_entry(VergenKey::GitCommitDate, cargo_rustc_env_map, cargo_warning);
748 }
749 if self.commit_message {
750 add_default_map_entry(
751 VergenKey::GitCommitMessage,
752 cargo_rustc_env_map,
753 cargo_warning,
754 );
755 }
756 if self.commit_timestamp {
757 add_default_map_entry(
758 VergenKey::GitCommitTimestamp,
759 cargo_rustc_env_map,
760 cargo_warning,
761 );
762 }
763 if self.describe {
764 add_default_map_entry(VergenKey::GitDescribe, cargo_rustc_env_map, cargo_warning);
765 }
766 if self.sha {
767 add_default_map_entry(VergenKey::GitSha, cargo_rustc_env_map, cargo_warning);
768 }
769 if self.dirty {
770 add_default_map_entry(VergenKey::GitDirty, cargo_rustc_env_map, cargo_warning);
771 }
772 Ok(())
773 }
774 }
775}
776
777#[cfg(test)]
778mod test {
779 use super::{Git2, Git2Builder};
780 use anyhow::Result;
781 use git2_rs::Repository;
782 use serial_test::serial;
783 #[cfg(unix)]
784 use std::io::stdout;
785 use std::{collections::BTreeMap, env::current_dir, io::Write};
786 use test_util::TestRepos;
787 #[cfg(unix)]
788 use test_util::TEST_MTIME;
789 use vergen::Emitter;
790 use vergen_lib::{count_idempotent, VergenKey};
791
792 #[test]
793 #[serial]
794 #[allow(clippy::clone_on_copy, clippy::redundant_clone)]
795 fn git2_clone_works() -> Result<()> {
796 let git2 = Git2Builder::all_git()?;
797 let another = git2.clone();
798 assert_eq!(another, git2);
799 Ok(())
800 }
801
802 #[test]
803 #[serial]
804 fn git2_debug_works() -> Result<()> {
805 let git2 = Git2Builder::all_git()?;
806 let mut buf = vec![];
807 write!(buf, "{git2:?}")?;
808 assert!(!buf.is_empty());
809 Ok(())
810 }
811
812 #[test]
813 #[serial]
814 fn git2_default() -> Result<()> {
815 let git2 = Git2Builder::default().build()?;
816 let emitter = Emitter::default().add_instructions(&git2)?.test_emit();
817 assert_eq!(0, emitter.cargo_rustc_env_map().len());
818 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
819 assert_eq!(0, emitter.cargo_warning().len());
820 Ok(())
821 }
822
823 #[test]
824 #[serial]
825 fn empty_email_is_default() -> Result<()> {
826 let mut cargo_rustc_env = BTreeMap::new();
827 let mut cargo_warning = vec![];
828 Git2::add_opt_value(
829 None,
830 VergenKey::GitCommitAuthorEmail,
831 &mut cargo_rustc_env,
832 &mut cargo_warning,
833 );
834 assert_eq!(1, cargo_rustc_env.len());
835 assert_eq!(1, cargo_warning.len());
836 Ok(())
837 }
838
839 #[test]
840 #[serial]
841 fn bad_revwalk_is_default() -> Result<()> {
842 let mut cargo_rustc_env = BTreeMap::new();
843 let mut cargo_warning = vec![];
844 let repo = Repository::discover(current_dir()?)?;
845 Git2::add_commit_count(true, &repo, &mut cargo_rustc_env, &mut cargo_warning);
846 assert_eq!(1, cargo_rustc_env.len());
847 assert_eq!(1, cargo_warning.len());
848 Ok(())
849 }
850
851 #[test]
852 #[serial]
853 fn head_not_found_is_default() -> Result<()> {
854 let test_repo = TestRepos::new(false, false, false)?;
855 let mut map = BTreeMap::new();
856 let mut cargo_warning = vec![];
857 let repo = Repository::discover(current_dir()?)?;
858 Git2::add_branch_name(true, &repo, &mut map, &mut cargo_warning)?;
859 assert_eq!(1, map.len());
860 assert_eq!(1, cargo_warning.len());
861 let mut map = BTreeMap::new();
862 let mut cargo_warning = vec![];
863 let repo = Repository::discover(test_repo.path())?;
864 Git2::add_branch_name(true, &repo, &mut map, &mut cargo_warning)?;
865 assert_eq!(1, map.len());
866 assert_eq!(1, cargo_warning.len());
867 Ok(())
868 }
869
870 #[test]
871 #[serial]
872 fn git_all_idempotent() -> Result<()> {
873 let git2 = Git2Builder::all_git()?;
874 let emitter = Emitter::default()
875 .idempotent()
876 .add_instructions(&git2)?
877 .test_emit();
878 assert_eq!(10, emitter.cargo_rustc_env_map().len());
879 assert_eq!(2, count_idempotent(emitter.cargo_rustc_env_map()));
880 assert_eq!(2, emitter.cargo_warning().len());
881 Ok(())
882 }
883
884 #[test]
885 #[serial]
886 fn git_all_shallow_clone() -> Result<()> {
887 let repo = TestRepos::new(false, false, true)?;
888 let mut git2 = Git2Builder::all_git()?;
889 let _ = git2.at_path(repo.path());
890 let emitter = Emitter::default().add_instructions(&git2)?.test_emit();
891 assert_eq!(10, emitter.cargo_rustc_env_map().len());
892 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
893 assert_eq!(0, emitter.cargo_warning().len());
894 Ok(())
895 }
896
897 #[test]
898 #[serial]
899 fn git_all_idempotent_no_warn() -> Result<()> {
900 let git2 = Git2Builder::all_git()?;
901 let emitter = Emitter::default()
902 .idempotent()
903 .quiet()
904 .add_instructions(&git2)?
905 .test_emit();
906
907 assert_eq!(10, emitter.cargo_rustc_env_map().len());
908 assert_eq!(2, count_idempotent(emitter.cargo_rustc_env_map()));
909 assert_eq!(2, emitter.cargo_warning().len());
910 Ok(())
911 }
912
913 #[test]
914 #[serial]
915 fn git_all() -> Result<()> {
916 let git2 = Git2Builder::all_git()?;
917 let emitter = Emitter::default().add_instructions(&git2)?.test_emit();
918 assert_eq!(10, emitter.cargo_rustc_env_map().len());
919 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
920 assert_eq!(0, emitter.cargo_warning().len());
921 Ok(())
922 }
923
924 #[test]
925 #[serial]
926 fn git_error_fails() -> Result<()> {
927 let mut git2 = Git2Builder::all_git()?;
928 let _ = git2.fail();
929 assert!(Emitter::default()
930 .fail_on_error()
931 .add_instructions(&git2)
932 .is_err());
933 Ok(())
934 }
935
936 #[test]
937 #[serial]
938 fn git_error_defaults() -> Result<()> {
939 let mut git2 = Git2Builder::all_git()?;
940 let _ = git2.fail();
941 let emitter = Emitter::default().add_instructions(&git2)?.test_emit();
942 assert_eq!(10, emitter.cargo_rustc_env_map().len());
943 assert_eq!(10, count_idempotent(emitter.cargo_rustc_env_map()));
944 assert_eq!(11, emitter.cargo_warning().len());
945 Ok(())
946 }
947
948 #[test]
949 #[serial]
950 fn source_date_epoch_works() {
951 temp_env::with_var("SOURCE_DATE_EPOCH", Some("1671809360"), || {
952 let result = || -> Result<()> {
953 let mut stdout_buf = vec![];
954 let gix = Git2Builder::default()
955 .commit_date(true)
956 .commit_timestamp(true)
957 .build()?;
958 _ = Emitter::new()
959 .idempotent()
960 .add_instructions(&gix)?
961 .emit_to(&mut stdout_buf)?;
962 let output = String::from_utf8_lossy(&stdout_buf);
963 for (idx, line) in output.lines().enumerate() {
964 if idx == 0 {
965 assert_eq!("cargo:rustc-env=VERGEN_GIT_COMMIT_DATE=2022-12-23", line);
966 } else if idx == 1 {
967 assert_eq!(
968 "cargo:rustc-env=VERGEN_GIT_COMMIT_TIMESTAMP=2022-12-23T15:29:20.000000000Z",
969 line
970 );
971 }
972 }
973 Ok(())
974 }();
975 assert!(result.is_ok());
976 });
977 }
978
979 #[test]
980 #[serial]
981 #[cfg(unix)]
982 fn bad_source_date_epoch_fails() {
983 use std::ffi::OsStr;
984 use std::os::unix::prelude::OsStrExt;
985
986 let source = [0x66, 0x6f, 0x80, 0x6f];
987 let os_str = OsStr::from_bytes(&source[..]);
988 temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
989 let result = || -> Result<bool> {
990 let mut stdout_buf = vec![];
991 let gix = Git2Builder::default().commit_date(true).build()?;
992 Emitter::new()
993 .idempotent()
994 .fail_on_error()
995 .add_instructions(&gix)?
996 .emit_to(&mut stdout_buf)
997 }();
998 assert!(result.is_err());
999 });
1000 }
1001
1002 #[test]
1003 #[serial]
1004 #[cfg(unix)]
1005 fn bad_source_date_epoch_defaults() {
1006 use std::ffi::OsStr;
1007 use std::os::unix::prelude::OsStrExt;
1008
1009 let source = [0x66, 0x6f, 0x80, 0x6f];
1010 let os_str = OsStr::from_bytes(&source[..]);
1011 temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
1012 let result = || -> Result<bool> {
1013 let mut stdout_buf = vec![];
1014 let gix = Git2Builder::default().commit_date(true).build()?;
1015 Emitter::new()
1016 .idempotent()
1017 .add_instructions(&gix)?
1018 .emit_to(&mut stdout_buf)
1019 }();
1020 assert!(result.is_ok());
1021 });
1022 }
1023
1024 #[test]
1025 #[serial]
1026 #[cfg(windows)]
1027 fn bad_source_date_epoch_fails() {
1028 use std::ffi::OsString;
1029 use std::os::windows::prelude::OsStringExt;
1030
1031 let source = [0x0066, 0x006f, 0xD800, 0x006f];
1032 let os_string = OsString::from_wide(&source[..]);
1033 let os_str = os_string.as_os_str();
1034 temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
1035 let result = || -> Result<bool> {
1036 let mut stdout_buf = vec![];
1037 let gix = Git2Builder::default().commit_date(true).build()?;
1038 Emitter::new()
1039 .fail_on_error()
1040 .idempotent()
1041 .add_instructions(&gix)?
1042 .emit_to(&mut stdout_buf)
1043 }();
1044 assert!(result.is_err());
1045 });
1046 }
1047
1048 #[test]
1049 #[serial]
1050 #[cfg(windows)]
1051 fn bad_source_date_epoch_defaults() {
1052 use std::ffi::OsString;
1053 use std::os::windows::prelude::OsStringExt;
1054
1055 let source = [0x0066, 0x006f, 0xD800, 0x006f];
1056 let os_string = OsString::from_wide(&source[..]);
1057 let os_str = os_string.as_os_str();
1058 temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
1059 let result = || -> Result<bool> {
1060 let mut stdout_buf = vec![];
1061 let gix = Git2Builder::default().commit_date(true).build()?;
1062 Emitter::new()
1063 .idempotent()
1064 .add_instructions(&gix)?
1065 .emit_to(&mut stdout_buf)
1066 }();
1067 assert!(result.is_ok());
1068 });
1069 }
1070
1071 #[test]
1072 #[serial]
1073 #[cfg(unix)]
1074 fn git_no_index_update() -> Result<()> {
1075 let repo = TestRepos::new(true, true, false)?;
1076 repo.set_index_magic_mtime()?;
1077
1078 let mut git2 = Git2Builder::default()
1079 .all()
1080 .describe(true, true, None)
1081 .build()?;
1082 let _ = git2.at_path(repo.path());
1083 let failed = Emitter::default()
1084 .add_instructions(&git2)?
1085 .emit_to(&mut stdout())?;
1086 assert!(!failed);
1087
1088 assert_eq!(*TEST_MTIME, repo.get_index_magic_mtime()?);
1089 Ok(())
1090 }
1091}