Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions crates/preloop-cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process::Command;

fn main() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
Expand Down Expand Up @@ -43,4 +44,52 @@ fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));
fs::write(out_dir.join("pins.rs"), out).expect("write pins.rs");
println!("cargo:rerun-if-changed=../../versions.toml");

// Embed the exact source commit this binary was built from. The updater
// uses it to resolve same-version installs: a source build from newer
// main reports the same version string as the latest release tag, and
// byte-comparing the binaries would clobber the newer build with the
// stale release asset. The commit is the monotonic signal the version
// string cannot carry. Release assets are built in CI from a git
// checkout, so the SHA is always present there; a non-git build (e.g.
// `cargo install` from a crates.io tarball) falls back to a sentinel
// that the updater treats as "cannot verify, keep what is installed".
let commit = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(&manifest_dir)
.output()
.ok()
.filter(|output| output.status.success())
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|sha| sha.trim().to_owned())
.filter(|sha| !sha.is_empty())
.unwrap_or_else(|| "unknown".to_owned());
println!("cargo:rustc-env=PRELOOP_BUILD_COMMIT={commit}");
// Make Cargo rebuild this crate when the checked-out commit changes.
// `.git/HEAD` alone is not enough: on a branch checkout it contains the
// symbolic ref (`ref: refs/heads/main`) whose contents do not change
// when the branch advances, so an incremental build would keep embedding
// the previous commit — and the updater could then compare that stale
// commit and clobber a newer source build with the release binary.
// Track HEAD, the ref it points at, and packed-refs (the ref file may
// live there instead of under refs/), all resolved through git itself.
let mut ref_paths = vec![String::from("../../.git/HEAD")];
if let Ok(output) = Command::new("git")
.args(["rev-parse", "--symbolic-full-name", "HEAD"])
.current_dir(&manifest_dir)
.output()
{
if output.status.success() {
if let Ok(ref_name) = String::from_utf8(output.stdout) {
let ref_name = ref_name.trim();
if let Some(short) = ref_name.strip_prefix("refs/") {
ref_paths.push(format!("../../.git/{short}"));
}
}
}
}
ref_paths.push(String::from("../../.git/packed-refs"));
for path in ref_paths {
println!("cargo:rerun-if-changed={path}");
}
}
6 changes: 5 additions & 1 deletion crates/preloop-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,11 @@ async fn main() -> anyhow::Result<()> {
// one underneath itself.
match cli.command {
Command::Version => {
println!("preloop {}", env!("CARGO_PKG_VERSION"));
println!(
"preloop {} ({})",
env!("CARGO_PKG_VERSION"),
env!("PRELOOP_BUILD_COMMIT")
);
return Ok(());
}
Command::Serve(args) => return cmd_engine(args).await,
Expand Down
6 changes: 6 additions & 0 deletions crates/preloop-cli/src/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,12 @@ mod tests {
b.path().to_str().unwrap(),
],
);
// The commit happens in B, and a clone does not inherit A's local
// config. Without this, the test only passes on hosts whose global
// git identity leaks into B (the CI fork has none, so it failed with
// "Author identity unknown").
git(b.path(), &["config", "user.email", "test@example.com"]);
git(b.path(), &["config", "user.name", "Test"]);
assert_eq!(
git(b.path(), &["rev-parse", "HEAD"]),
head,
Expand Down
Loading
Loading