tix-tracker 0.12.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
//! Bakes a **build stamp** into the binary so `tix version` can answer "did this
//! binary come from the code on disk?" — the question `tix --version` cannot,
//! because a version number only moves when someone bumps it.
//!
//! ## Why there is deliberately no `cargo:rerun-if-changed` directive
//!
//! With none emitted, cargo reruns this script whenever **any file in the
//! package** changes — which is exactly when a rebuild happens, so the stamp
//! moves precisely when the binary does. Pinning it to `build.rs` alone (the
//! usual tightening) would freeze the stamp at "the last time `build.rs`
//! changed", and a frozen stamp is worse than none: it would report a stale
//! commit for a fresh binary, which is the failure it exists to catch.
//!
//! Both values are read from the environment at compile time
//! (`env!("TIX_BUILD_COMMIT")`, `env!("TIX_BUILD_UNIX")`), so they cost nothing
//! at runtime — no `git` call, no clock read, when the command runs.

use std::process::Command;

fn main() {
    // Everything else this uses is a rustc-env, so `Cargo.toml` moves the stamp
    // too (a version bump is a reason to rebuild).
    let commit =
        capture(&["rev-parse", "--short", "HEAD"]).unwrap_or_else(|| "unknown".to_string());
    // A working tree with uncommitted changes is worth marking: the binary came
    // from something that is not in any commit.
    let dirty = capture(&["status", "--porcelain"]).is_some_and(|status| !status.is_empty());
    println!(
        "cargo:rustc-env=TIX_BUILD_COMMIT={commit}{}",
        if dirty { "-dirty" } else { "" }
    );

    let seconds = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map_or(0, |elapsed| elapsed.as_secs());
    println!("cargo:rustc-env=TIX_BUILD_UNIX={seconds}");
}

/// The trimmed stdout of a `git` invocation, or `None` when git is absent or the
/// command failed (a source tarball has no repository, and that must not fail the
/// build).
fn capture(args: &[&str]) -> Option<String> {
    let output = Command::new("git").args(args).output().ok()?;
    if !output.status.success() {
        return None;
    }
    let text = String::from_utf8_lossy(&output.stdout).trim().to_string();
    (!text.is_empty()).then_some(text)
}