]> quire.kejadlen.dev Git - quire.git/commitdiff
Push actual refs to mirror instead of hardcoding main
authorAlpha Chen <alpha@kejadlen.dev>
Mon, 27 Apr 2026 15:29:38 +0000 (15:29 +0000)
committerAlpha Chen <alpha@kejadlen.dev>
Mon, 27 Apr 2026 15:44:55 +0000 (08:44 -0700)
The post-receive hook now parses stdin to find which refs were updated
and pushes only those. This handles repos with any default branch name,
not just main. Deleted refs (all-zero new sha) are skipped.

Assisted-by: GLM-5.1 via pi
src/bin/quire/commands/hook.rs
src/quire.rs

index dc654e9b5d9c7d5d47336755288241a8d80ab99b..1c1cf17c48b478c7ed6e15dfeb3e55cdc6f8de6c 100644 (file)
@@ -58,8 +58,31 @@ fn post_receive(quire: &Quire) -> Result<()> {
         .reveal()
         .context("failed to resolve GitHub token")?;
 
-    tracing::info!(url = %mirror.url, "pushing to mirror");
-    repo.push_to_mirror(&mirror, token)?;
+    // Parse pushed refs from stdin. Each line is:
+    //   <old-sha> <new-sha> <refname>
+    // Only push refs that were actually updated (new sha is not all zeros).
+    let stdin = io::stdin();
+    let mut refs: Vec<String> = Vec::new();
+    for line in stdin.lines() {
+        let line = line.map_err(|e| miette!("failed to read hook stdin: {e}"))?;
+        let parts: Vec<&str> = line.split_whitespace().collect();
+        if parts.len() != 3 {
+            continue;
+        }
+        let new_sha = parts[1];
+        if new_sha == "0000000000000000000000000000000000000000" {
+            continue;
+        }
+        refs.push(parts[2].to_string());
+    }
+
+    if refs.is_empty() {
+        return Ok(());
+    }
+
+    let ref_slices: Vec<&str> = refs.iter().map(|s| s.as_str()).collect();
+    tracing::info!(url = %mirror.url, refs = ?ref_slices, "pushing to mirror");
+    repo.push_to_mirror(&mirror, token, &ref_slices)?;
     tracing::info!(url = %mirror.url, "mirror push complete");
     Ok(())
 }
index 398fe6693bd5b61ce465295884539856bb2a8ccf..563dc9ef8398279161fae64beed9d6de8145499b 100644 (file)
@@ -93,9 +93,12 @@ impl Repo {
     /// but it remains visible in `/proc/<pid>/environ` to anything running
     /// as the same uid for the lifetime of the push. Acceptable today
     /// (single-user container, no CI runner yet); revisit when CI lands.
-    pub fn push_to_mirror(&self, mirror: &MirrorConfig, token: &str) -> crate::Result<()> {
+    pub fn push_to_mirror(&self, mirror: &MirrorConfig, token: &str, refs: &[&str]) -> crate::Result<()> {
+        let mut args = vec!["push", "--porcelain", &mirror.url];
+        args.extend(refs);
+
         let status = self
-            .git(&["push", "--porcelain", &mirror.url, "main"])
+            .git(&args)
             .env("GIT_CONFIG_COUNT", "1")
             .env("GIT_CONFIG_KEY_0", "http.extraHeader")
             .env(
@@ -717,7 +720,7 @@ mod tests {
         let mirror = MirrorConfig {
             url: format!("file://{}", target.display()),
         };
-        repo.push_to_mirror(&mirror, "ignored-for-file-url")
+        repo.push_to_mirror(&mirror, "ignored-for-file-url", &["main"])
             .expect("push should succeed");
 
         assert_eq!(rev_parse(&source, "main"), rev_parse(&target, "main"));
@@ -735,7 +738,7 @@ mod tests {
         let mirror = MirrorConfig {
             url: "file:///nonexistent/quire-test/target.git".to_string(),
         };
-        let err = repo.push_to_mirror(&mirror, "x").unwrap_err();
+        let err = repo.push_to_mirror(&mirror, "x", &["main"]).unwrap_err();
         assert!(
             matches!(err, crate::Error::Git(_)),
             "expected Git error, got {err:?}"