Gunnar

nordisk/Skidbladnir

build.rs

//! Build script: stamp the git short-hash for the `--version` line.
//!
//! Pure-Rust (`pure-rust-zero-shell-law`): reads the on-disk git plumbing
//! (`.git/HEAD`, the pointed-to loose ref, `packed-refs`) with `std::fs` — NEVER
//! `git` / `std::process::Command`. Handles detached HEAD, packed refs and linked
//! worktrees, and degrades to `unknown` (never fails the build) when no git
//! metadata is present. Emits `SKIDBLADNIR_GIT_HASH` for `env!()` to bake in
//! (build-version-githash-law: EVERY binary says which commit built it).

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

fn main() {
    let hash = git_short_hash().unwrap_or_else(|| "unknown".to_string());
    println!("cargo:rustc-env=SKIDBLADNIR_GIT_HASH={hash}");
}

/// Best-effort short commit hash, reading only the on-disk git plumbing.
fn git_short_hash() -> Option<String> {
    let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?);
    let git_dir = find_git_dir(&manifest_dir)?;
    let common_dir = common_dir(&git_dir);

    let head_path = git_dir.join("HEAD");
    rerun_if_changed(&head_path);
    let head = fs::read_to_string(&head_path).ok()?;
    let head = head.trim();

    let full = if let Some(refname) = head.strip_prefix("ref:") {
        resolve_ref(refname.trim(), &git_dir, &common_dir)?
    } else {
        // Detached HEAD: the file holds the commit id directly.
        head.to_string()
    };

    let full = full.trim();
    if full.is_empty() {
        return None;
    }
    Some(full.chars().take(7).collect())
}

/// Walk up from `start` until a `.git` directory or `.git` gitdir-file is found.
fn find_git_dir(start: &Path) -> Option<PathBuf> {
    let mut dir = Some(start);
    while let Some(d) = dir {
        let dot_git = d.join(".git");
        if dot_git.is_dir() {
            return Some(dot_git);
        }
        if dot_git.is_file() {
            // Linked worktree: `.git` is a file `gitdir: <path>`.
            let contents = fs::read_to_string(&dot_git).ok()?;
            let target = contents.trim().strip_prefix("gitdir:")?.trim();
            return Some(normalize(&d.join(target)));
        }
        dir = d.parent();
    }
    None
}

/// The shared common git dir for a (possibly linked-worktree) git dir.
fn common_dir(git_dir: &Path) -> PathBuf {
    if let Ok(contents) = fs::read_to_string(git_dir.join("commondir")) {
        return normalize(&git_dir.join(contents.trim()));
    }
    git_dir.to_path_buf()
}

/// Resolve a `refs/…` name to a commit id: loose ref file first, then `packed-refs`.
fn resolve_ref(refname: &str, git_dir: &Path, common_dir: &Path) -> Option<String> {
    for base in [git_dir, common_dir] {
        let loose = base.join(refname);
        if loose.is_file() {
            rerun_if_changed(&loose);
            if let Ok(s) = fs::read_to_string(&loose) {
                let s = s.trim();
                if !s.is_empty() {
                    return Some(s.to_string());
                }
            }
        }
    }
    let packed = common_dir.join("packed-refs");
    if packed.is_file() {
        rerun_if_changed(&packed);
        if let Ok(contents) = fs::read_to_string(&packed) {
            for line in contents.lines() {
                let line = line.trim();
                if line.is_empty() || line.starts_with('#') || line.starts_with('^') {
                    continue;
                }
                if let Some((sha, name)) = line.split_once(' ') {
                    if name.trim() == refname {
                        return Some(sha.trim().to_string());
                    }
                }
            }
        }
    }
    None
}

/// Lightweight lexical `.`/`..` normalization.
fn normalize(path: &Path) -> PathBuf {
    let mut out = PathBuf::new();
    for comp in path.components() {
        match comp {
            std::path::Component::ParentDir => {
                out.pop();
            }
            std::path::Component::CurDir => {}
            other => out.push(other.as_os_str()),
        }
    }
    out
}

fn rerun_if_changed(path: &Path) {
    println!("cargo:rerun-if-changed={}", path.display());
}