use std::io::{self, BufRead}; use std::process::Command; use anyhow::{Context, Result, bail}; fn main() { if let Err(e) = run() { eprintln!("relay: {e:#}"); std::process::exit(1); } } fn run() -> Result<()> { let mirror_remote = get_mirror_remote()?; let refs = read_pushed_refs()?; if refs.is_empty() { return Ok(()); } push_to_mirror(&mirror_remote, &refs) } fn get_mirror_remote() -> Result { let output = Command::new("git") .args(["config", "relay.mirror-remote"]) .output() .context("failed to run git config")?; if !output.status.success() { bail!( "relay.mirror-remote is not configured\n \ Set it with: git config relay.mirror-remote " ); } Ok(String::from_utf8(output.stdout) .context("git config output is not valid UTF-8")? .trim() .to_string()) } struct PushedRef { local_ref: String, local_sha: String, remote_ref: String, } fn read_pushed_refs() -> Result> { let stdin = io::stdin(); let mut refs = Vec::new(); for line in stdin.lock().lines() { let line = line.context("failed to read stdin")?; let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() >= 4 { refs.push(PushedRef { local_ref: parts[0].to_string(), local_sha: parts[1].to_string(), remote_ref: parts[2].to_string(), }); } } Ok(refs) } fn push_to_mirror(remote: &str, refs: &[PushedRef]) -> Result<()> { let zero_sha = "0".repeat(40); let refspecs: Vec = refs .iter() .map(|r| { if r.local_sha == zero_sha { format!(":{}", r.remote_ref) } else { format!("{}:{}", r.local_ref, r.remote_ref) } }) .collect(); let status = Command::new("git") .arg("push") .arg(remote) .args(&refspecs) .status() .context("failed to execute git push")?; if !status.success() { bail!("mirror push to '{}' failed", remote); } Ok(()) }