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 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
//! # Generate a testament of the git working tree state for a build
//!
//! You likely want to see either the [git_testament] macro, or if you
//! are in a no-std type situation, the [git_testament_macros] macro instead.
//!
//! [git_testament]: macro.git_testament.html
//! [git_testament_macros]: macro.git_testament_macros.html
//!
//! If you build this library with the default `alloc` feature disabled then while
//! the non-macro form of the testaments are offered, they cannot be rendered
//! and the [render_testament] macro will not be provided.
//!
//! [render_testament]: macro.render_testament.html
//!
//! ## Trusted branches
//!
//! In both [render_testament] and [git_testament_macros] you will find mention
//! of the concept of a "trusted" branch. This exists as a way to allow releases
//! to be made from branches which are not yet tagged. For example, if your
//! release process requires that the release binaries be built and tested
//! before tagging the repository then by nominating a particular branch as
//! trusted, you can cause the rendered testament to trust the crate's version
//! rather than being quite noisy about how the crate version and the tag
//! version do not match up.
#![no_std]
extern crate no_std_compat as std;
use std::prelude::v1::*;
pub use git_testament_derive::{git_testament, git_testament_macros};
use std::fmt::{self, Display, Formatter};
/// 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,
};
#[cfg(feature = "alloc")]
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.contains(pkg_version) {
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"));
/// # }
#[cfg(feature = "alloc")]
#[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) => {
write!(fmt, "{crate_ver} ({build_date})")
}
CommitKind::NoCommit(crate_ver, build_date) => {
write!(fmt, "{crate_ver} (uncommitted {build_date})")
}
CommitKind::NoTags(commit, when) => {
write!(fmt, "unknown ({} {})", &commit[..9], when)
}
CommitKind::FromTag(tag, commit, when, depth) => {
if *depth > 0 {
write!(fmt, "{}+{} ({} {})", tag, depth, &commit[..9], when)
} else {
write!(fmt, "{} ({} {})", 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() {
write!(
fmt,
" dirty {} modification{}",
self.modifications.len(),
if self.modifications.len() > 1 {
"s"
} else {
""
}
)?;
}
Ok(())
}
}