znippy-cli 0.9.10

CLI for Znippy, a parallel chunked compression system.
Documentation
// build.rs — stamp the commit identity into the `znippy` binary.
//
// Reads the short git SHA from the git metadata on disk with **pure
// `std::fs`** — NO `Command::new("git")`, per the pure-Rust / zero-shell law
// (build code is production code; it must not shell out). Handles a symbolic or
// detached HEAD, `packed-refs`, and linked worktrees (`.git` as a pointer
// file). znippy-cli lives in a workspace subdir, so we walk up from the crate
// manifest to find the repo's `.git`. Falls back to `unknown` off a checkout.
//
// Emits `ZNIPPY_GIT_HASH` (via `cargo:rustc-env`) which `lib.rs` folds into the
// `--version` string with `env!`.

use std::path::{Path, PathBuf};

fn main() {
    let hash = git_short_hash().unwrap_or_else(|| "unknown".to_string());
    println!("cargo:rustc-env=ZNIPPY_GIT_HASH={hash}");
    println!("cargo:rerun-if-changed=build.rs");
}

/// Read the short (7-char) commit hash from git metadata on disk without
/// shelling out. Returns `None` off a git checkout.
fn git_short_hash() -> Option<String> {
    let (gitdir, commondir) = resolve_git_dirs()?;
    println!("cargo:rerun-if-changed={}", gitdir.join("HEAD").display());
    let head = std::fs::read_to_string(gitdir.join("HEAD")).ok()?;
    let head = head.trim();
    let full = if let Some(ref_name) = head.strip_prefix("ref: ") {
        // Symbolic HEAD → resolve the branch ref (loose file or packed-refs).
        resolve_ref(ref_name.trim(), &gitdir, &commondir)?
    } else {
        // Detached HEAD: the file IS the object id.
        head.to_string()
    };
    let full = full.trim();
    if full.len() < 7 || !full.bytes().all(|b| b.is_ascii_hexdigit()) {
        return None;
    }
    Some(full[..7].to_string())
}

/// Walk up from the crate manifest dir to find `.git`, then resolve the real
/// gitdir and the common dir. For a normal checkout both are `<root>/.git`. For
/// a linked worktree `.git` is a FILE (`gitdir: <path>`) and the shared refs
/// live in the common dir named by `<gitdir>/commondir`.
fn resolve_git_dirs() -> Option<(PathBuf, PathBuf)> {
    let manifest = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR")?);
    let mut dir: Option<&Path> = Some(manifest.as_path());
    let mut dot_git = None;
    while let Some(d) = dir {
        let candidate = d.join(".git");
        if candidate.exists() {
            dot_git = Some(candidate);
            break;
        }
        dir = d.parent();
    }
    let dot_git = dot_git?;
    let meta = std::fs::metadata(&dot_git).ok()?;
    let gitdir = if meta.is_dir() {
        dot_git
    } else {
        println!("cargo:rerun-if-changed={}", dot_git.display());
        let contents = std::fs::read_to_string(&dot_git).ok()?;
        let rel = contents.trim().strip_prefix("gitdir:")?.trim();
        let p = PathBuf::from(rel);
        // `gitdir:` may be relative to the `.git` pointer file's parent.
        if p.is_absolute() {
            p
        } else {
            dot_git.parent()?.join(p)
        }
    };
    let commondir = match std::fs::read_to_string(gitdir.join("commondir")) {
        Ok(rel) => {
            let rel = rel.trim();
            let p = PathBuf::from(rel);
            if p.is_absolute() { p } else { gitdir.join(p) }
        }
        Err(_) => gitdir.clone(),
    };
    Some((gitdir, commondir))
}

/// Resolve a symbolic ref (e.g. `refs/heads/master`) to an object id: try the
/// loose ref file under the gitdir, then the common dir, then `packed-refs`.
fn resolve_ref(ref_name: &str, gitdir: &Path, commondir: &Path) -> Option<String> {
    for base in [gitdir, commondir] {
        let loose = base.join(ref_name);
        println!("cargo:rerun-if-changed={}", loose.display());
        if let Ok(s) = std::fs::read_to_string(&loose) {
            let s = s.trim();
            if !s.is_empty() && !s.starts_with("ref: ") {
                return Some(s.to_string());
            }
        }
    }
    let packed = commondir.join("packed-refs");
    println!("cargo:rerun-if-changed={}", packed.display());
    let packed = std::fs::read_to_string(&packed).ok()?;
    for line in packed.lines() {
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') || line.starts_with('^') {
            continue;
        }
        // Each entry is "<sha> <refname>".
        if let Some((sha, name)) = line.split_once(' ') {
            if name.trim() == ref_name {
                return Some(sha.trim().to_string());
            }
        }
    }
    None
}