serve/build.rs
// Apache-2.0 licensed. See LICENSE-APACHE.
//! Pure-filesystem git-commit stamp for the `skade-katalog` serve binary.
//!
//! Reads the commit id straight out of `.git/` — **no `git` subprocess** — so it
//! honors the nordisk zero-shell law (no shelling out, ever). Emits
//! `cargo:rustc-env=SKADE_GIT_HASH=<short-sha>` for the binary to `env!()`, plus
//! `rerun-if-changed` on every file it reads so the stamp refreshes on commit.
//!
//! Handles the cases that actually occur:
//! * a normal `.git/` directory with a loose ref;
//! * a ref that lives only in `packed-refs`;
//! * a detached HEAD (raw commit id in `.git/HEAD`);
//! * a linked worktree / submodule where `.git` is a *file* pointing at a
//! `gitdir:` and refs resolve against the `commondir`.
//!
//! When no repository is present (e.g. a `cargo publish` tarball build), it
//! falls back to `unknown` rather than failing the build.
use std::fs;
use std::path::{Path, PathBuf};
fn main() {
let hash = git_hash().unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=SKADE_GIT_HASH={hash}");
}
/// Locate the `.git` store for this crate: walk up from the manifest dir, and
/// follow a `gitdir:` pointer file when `.git` is a file (worktree/submodule).
fn git_dir() -> Option<PathBuf> {
let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?);
let mut cur: Option<&Path> = Some(manifest.as_path());
while let Some(dir) = cur {
let dot = dir.join(".git");
if dot.is_dir() {
return Some(dot);
}
if dot.is_file() {
let content = fs::read_to_string(&dot).ok()?;
let rest = content.strip_prefix("gitdir:")?.trim();
let gitdir = dir.join(rest);
return Some(fs::canonicalize(&gitdir).unwrap_or(gitdir));
}
cur = dir.parent();
}
None
}
/// The shared object/ref store. For a linked worktree, `.git/commondir` points
/// at the main `.git` where `refs/` and `packed-refs` live; otherwise it is the
/// git dir itself.
fn common_dir(git: &Path) -> PathBuf {
if let Ok(s) = fs::read_to_string(git.join("commondir")) {
let p = PathBuf::from(s.trim());
let joined = if p.is_absolute() { p } else { git.join(p) };
return fs::canonicalize(&joined).unwrap_or(joined);
}
git.to_path_buf()
}
fn git_hash() -> Option<String> {
let git = git_dir()?;
let common = common_dir(&git);
let head_path = git.join("HEAD");
println!("cargo:rerun-if-changed={}", head_path.display());
let head = fs::read_to_string(&head_path).ok()?;
let head = head.trim();
let full = match head.strip_prefix("ref:") {
// Symbolic HEAD, e.g. `ref: refs/heads/main`.
Some(ref_path) => resolve_ref(&common, ref_path.trim())?,
// Detached HEAD: the raw commit id is in HEAD.
None => head.to_string(),
};
Some(full.chars().take(12).collect())
}
/// Resolve a ref name (`refs/heads/main`) to a commit id: a loose ref file
/// first, then `packed-refs`.
fn resolve_ref(common: &Path, ref_path: &str) -> Option<String> {
let loose = common.join(ref_path);
println!("cargo:rerun-if-changed={}", loose.display());
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.join("packed-refs");
println!("cargo:rerun-if-changed={}", packed.display());
let content = fs::read_to_string(&packed).ok()?;
for line in content.lines() {
let line = line.trim();
// Skip comments and `^`-prefixed peeled-tag lines.
if line.is_empty() || line.starts_with('#') || line.starts_with('^') {
continue;
}
if let Some((sha, name)) = line.split_once(' ') {
if name == ref_path {
return Some(sha.to_string());
}
}
}
None
}