1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
//! # Generate a testament of the git working tree state for a build //! use std::fmt::{self, Display, Formatter}; pub use git_testament_derive::git_testament; /// A modification to a working tree, recorded when the testament was created. #[derive(Debug)] pub enum GitModification<'a> { /// A file or directory was added but not committed Added(&'a [u8]), /// A file or directory was removed but not committed Removed(&'a [u8]), /// A file was modified in some way, either content or permissions Modified(&'a [u8]), /// A file or directory was present but untracked Untracked(&'a [u8]), } /// The kind of commit available at the point that the testament was created. #[derive(Debug)] pub enum CommitKind<'a> { /// No repository was present. Instead the crate's version and the /// build date are recorded. NoRepository(&'a str, &'a str), /// No commit was present, though it was a repository. Instead the crate's /// version and the build date are recorded. NoCommit(&'a str, &'a str), /// There are no tags in the repository in the history of the commit. /// The commit hash and commit date are recorded. NoTags(&'a str, &'a str), /// There were tags in the history of the commit. /// The tag name, commit hash, commit date, and distance from the tag to /// the commit are recorded. FromTag(&'a str, &'a str, &'a str, usize), } /// A testament to the state of a git repository when a crate is built. /// /// This is the type returned by the [`git_testament_derive::git_testament`] /// macro when used to record the state of a git tree when a crate is built. /// /// The structure contains information about the commit from which the crate /// was built, along with information about any modifications to the working /// tree which could be considered "dirty" as a result. /// /// By default, the `Display` implementation for this structure attempts to /// produce something pleasant but useful to humans. For example it might /// produce a string along the lines of `"1.0.0 (763aa159d 2019-04-02)"` for /// a clean build from a 1.0.0 tag. Alternatively if the working tree is dirty /// and there have been some commits since the last tag, you might get something /// more like `"1.0.0+14 (651af89ed 2019-04-02) dirty 4 modifications"` /// /// If your program wishes to go into more detail, then the `commit` and the /// `modifications` members are available for rendering as the program author /// sees fit. /// /// In general this is only of use for binaries, since libraries will generally /// be built from `crates.io` provided tarballs and as such won't carry the /// information needed. In such a fallback position the string will be something /// along the lines of `"x.y (somedate)"` where `x.y` is the crate's version and /// `somedate` is the date of the build. You'll get similar information if the /// crate is built in a git repository on a branch with no commits yet (e.g. /// when you first have run `cargo init`) though that will include the string /// `uncommitted` to indicate that once commits are made the information will be /// of more use. #[derive(Debug)] pub struct GitTestament<'a> { pub commit: CommitKind<'a>, pub modifications: &'a [GitModification<'a>], pub branch_name: Option<&'a str>, } /// An empty testament. /// /// This is used by the derive macro to fill in defaults /// in the case that an older derive macro is used with a newer version /// of git_testament. /// /// Typically this will not be used directly by a user. pub const EMPTY_TESTAMENT: GitTestament = GitTestament { commit: CommitKind::NoRepository("unknown", "unknown"), modifications: &[], branch_name: None, }; impl<'a> GitTestament<'a> { #[doc(hidden)] pub fn _render_with_version( &self, pkg_version: &str, trusted_branch: Option<&'static str>, ) -> String { match self.commit { CommitKind::FromTag(tag, hash, date, _) => { let trusted = match trusted_branch { Some(_) => { if self.branch_name == trusted_branch { self.modifications.is_empty() } else { false } } None => false, }; if trusted { // We trust our branch, so construct an equivalent // testament to render format!( "{}", GitTestament { commit: CommitKind::FromTag(pkg_version, hash, date, 0), ..*self } ) } else if tag.find(&pkg_version).is_some() { format!("{}", self) } else { format!("{} :: {}", pkg_version, self) } } _ => format!("{}", self), } } } /// Render a testament /// /// This macro can be used to render a testament created with the `git_testament` /// macro. It renders a testament with the added benefit of indicating if the /// tag does not match the version (by substring) then the crate's version and /// the tag will be displayed in the form: "crate-ver :: testament..." /// /// For situations where the crate version MUST override the tag, for example /// if you have a release process where you do not make the tag unless the CI /// constructing the release artifacts passes, then you can pass a second /// argument to this macro stating a branch name to trust. If the working /// tree is clean and the branch name matches then the testament is rendered /// as though the tag had been pushed at the built commit. Since this overrides /// a fundamental part of the behaviour of `git_testament` it is recommended that /// this *ONLY* be used if you have a trusted CI release branch process. /// /// ``` /// use git_testament::{git_testament, render_testament}; /// /// git_testament!(TESTAMENT); /// /// # fn main() { /// println!("The testament is: {}", render_testament!(TESTAMENT)); /// println!("The fiddled testament is: {}", render_testament!(TESTAMENT, "trusted-branch")); /// # } #[macro_export] macro_rules! render_testament { ( $testament:expr ) => { $testament._render_with_version(env!("CARGO_PKG_VERSION"), None) }; ( $testament:expr, $trusted_branch:expr ) => { $testament._render_with_version(env!("CARGO_PKG_VERSION"), Some($trusted_branch)) }; } impl<'a> Display for CommitKind<'a> { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { match self { CommitKind::NoRepository(crate_ver, build_date) => { fmt.write_fmt(format_args!("{} ({})", crate_ver, build_date)) } CommitKind::NoCommit(crate_ver, build_date) => { fmt.write_fmt(format_args!("{} (uncommitted {})", crate_ver, build_date)) } CommitKind::NoTags(commit, when) => { fmt.write_fmt(format_args!("unknown ({} {})", &commit[..9], when)) } CommitKind::FromTag(tag, commit, when, depth) => { if *depth > 0 { fmt.write_fmt(format_args!( "{}+{} ({} {})", tag, depth, &commit[..9], when )) } else { fmt.write_fmt(format_args!("{} ({} {})", tag, &commit[..9], when)) } } } } } impl<'a> Display for GitTestament<'a> { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { self.commit.fmt(fmt)?; if !self.modifications.is_empty() { fmt.write_fmt(format_args!( " dirty {} modification{}", self.modifications.len(), if self.modifications.len() > 1 { "s" } else { "" } ))?; } Ok(()) } }