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 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
//! # 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]
#[cfg(feature = "alloc")]
extern crate alloc;
#[doc(hidden)]
pub extern crate core as __core;
#[doc(hidden)]
pub extern crate git_testament_derive as __derive;
use core::fmt::{self, Display, Formatter};
// Clippy thinks our fn main() is needless, but it is needed because otherwise
// we cannot have the invocation of the procedural macro (yet)
#[allow(clippy::needless_doctest_main)]
/// Generate a testament for the working tree.
///
/// This macro declares a static data structure which represents a testament
/// to the state of a git repository at the point that a crate was built.
///
/// The intention is that the macro should be used at the top level of a binary
/// crate to provide information about the state of the codebase that the output
/// program was built from. This includes a number of things such as the commit
/// SHA, any related tag, how many commits since the tag, the date of the commit,
/// and if there are any "dirty" parts to the working tree such as modified files,
/// uncommitted files, etc.
///
/// ```
/// // Bring the procedural macro into scope
/// use git_testament::git_testament;
///
/// // Declare a testament, it'll end up as a static, so give it a capital
/// // letters name or it'll result in a warning.
/// git_testament!(TESTAMENT);
/// # fn main() {
///
/// // ... later, you can display the testament.
/// println!("app version {TESTAMENT}");
/// # }
/// ```
///
/// See [`GitTestament`] for the type of the defined `TESTAMENT`.
#[macro_export]
macro_rules! git_testament {
($name:ident) => {
$crate::__derive::git_testament! {
$crate $name
}
};
}
// Clippy thinks our fn main() is needless, but it is needed because otherwise
// we cannot have the invocation of the procedural macro (yet)
#[allow(clippy::needless_doctest_main)]
/// Generate a testament for the working tree as a set of static string macros.
///
/// This macro declares a set of macros which provide you with your testament
/// as static strings.
///
/// The intention is that the macro should be used at the top level of a binary
/// crate to provide information about the state of the codebase that the output
/// program was built from. This includes a number of things such as the commit
/// SHA, any related tag, how many commits since the tag, the date of the commit,
/// and if there are any "dirty" parts to the working tree such as modified files,
/// uncommitted files, etc.
///
/// ```
/// // Bring the procedural macro into scope
/// use git_testament::git_testament_macros;
///
/// // Declare a testament, it'll end up as pile of macros, so you can
/// // give it whatever ident-like name you want. The name will prefix the
/// // macro names. Also you can optionally specify
/// // a branch name which will be considered the "trusted" branch like in
/// // `git_testament::render_testament!()`
/// git_testament_macros!(version);
/// # fn main() {
///
/// // ... later, you can display the testament.
/// println!("app version {}", version_testament!());
/// # }
/// ```
///
/// The macros all resolve to string literals, boolean literals, or in the case
/// of `NAME_tag_distance!()` a number. This is most valuable when you are
/// wanting to include the information into a compile-time-constructed string
///
/// ```
/// // Bring the procedural macro into scope
/// use git_testament::git_testament_macros;
///
/// // Declare a testament, it'll end up as pile of macros, so you can
/// // give it whatever ident-like name you want. The name will prefix the
/// // macro names. Also you can optionally specify
/// // a branch name which will be considered the "trusted" branch like in
/// // `git_testament::render_testament!()`
/// git_testament_macros!(version, "stable");
///
/// const APP_VERSION: &str = concat!("app version ", version_testament!());
/// # fn main() {
///
/// // ... later, you can display the testament.
/// println!("{APP_VERSION}");
/// # }
/// ```
///
/// The set of macros defined is:
///
/// * `NAME_testament!()` -> produces a string similar but not guaranteed to be
/// identical to the result of `Display` formatting a normal testament.
/// * `NAME_branch!()` -> An Option<&str> of the current branch name
/// * `NAME_repo_present!()` -> A boolean indicating if there is a repo at all
/// * `NAME_commit_present!()` -> A boolean indicating if there is a commit present at all
/// * `NAME_tag_present!()` -> A boolean indicating if there is a tag present
/// * `NAME_commit_hash!()` -> A string of the commit hash (or crate version if commit not present)
/// * `NAME_commit_date!()` -> A string of the commit date (or build date if no commit present)
/// * `NAME_tag_name!()` -> The tag name if present (or crate version if commit not present)
/// * `NAME_tag_distance!()` -> The number of commits since the tag if present (zero otherwise)
#[macro_export]
macro_rules! git_testament_macros {
($name:ident $(, $trusted:literal)?) => {
$crate::__derive::git_testament_macros! {
$crate $name $($trusted)?
}
};
}
/// 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>,
) -> alloc::string::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
alloc::format!(
"{}",
GitTestament {
commit: CommitKind::FromTag(pkg_version, hash, date, 0),
..*self
}
)
} else if tag.contains(pkg_version) {
alloc::format!("{self}")
} else {
alloc::format!("{pkg_version} :: {self}")
}
}
_ => alloc::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 ) => {
$crate::GitTestament::_render_with_version(
&$testament,
$crate::__core::env!("CARGO_PKG_VERSION"),
$crate::__core::option::Option::None,
)
};
( $testament:expr, $trusted_branch:expr ) => {
$crate::GitTestament::_render_with_version(
&$testament,
$crate::__core::env!("CARGO_PKG_VERSION"),
$crate::__core::option::Option::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(())
}
}