Simplify mirror: push all updated refs, drop branch filter and tag creation
Per review: just mirror the pushed ref as-is. Removes the `github.branch`
config key and all tag-creation logic from the mirror path.
https://claude.ai/code/session_01MtUMXi7Z3GCDWQFY8puWpu
diff --git a/quire-core/src/ci/repo_config.rs b/quire-core/src/ci/repo_config.rs
index a1899c9..bba9f78 100644
--- a/quire-core/src/ci/repo_config.rs
+++ b/quire-core/src/ci/repo_config.rs
@@ -11,26 +11,12 @@ pub struct RepoConfig {
}
/// Per-repo GitHub configuration.
-#[derive(serde::Deserialize, Debug, Clone)]
+#[derive(serde::Deserialize, Debug, Default, Clone)]
#[serde(default, rename_all = "kebab-case")]
pub struct GithubRepoConfig {
- /// Remote URL to mirror to on every push to `:branch`.
+ /// Remote URL to mirror every pushed ref to.
/// E.g. `"https://github.com/user/repo.git"`.
- /// When set, quire injects a `quire/mirror` built-in job into
- /// every pipeline for this repo.
pub mirror: Option<String>,
- /// Ref that triggers the mirror (default: `refs/heads/main`).
- /// Pushes to any other ref are ignored by the mirror job.
- pub branch: String,
-}
-
-impl Default for GithubRepoConfig {
- fn default() -> Self {
- Self {
- mirror: None,
- branch: "refs/heads/main".to_string(),
- }
- }
}
#[cfg(test)]
@@ -48,7 +34,6 @@ mod tests {
fn defaults_when_empty_table() {
let cfg = load("{}");
assert!(cfg.github.mirror.is_none());
- assert_eq!(cfg.github.branch, "refs/heads/main");
}
#[test]
@@ -59,18 +44,4 @@ mod tests {
Some("https://github.com/user/repo.git")
);
}
-
- #[test]
- fn parses_custom_branch() {
- let cfg = load(
- r#"{:github {:mirror "https://github.com/u/r.git" :branch "refs/heads/release"}}"#,
- );
- assert_eq!(cfg.github.branch, "refs/heads/release");
- }
-
- #[test]
- fn default_branch_when_only_mirror_set() {
- let cfg = load(r#"{:github {:mirror "https://github.com/u/r.git"}}"#);
- assert_eq!(cfg.github.branch, "refs/heads/main");
- }
}
diff --git a/quire-server/src/mirror.rs b/quire-server/src/mirror.rs
index d8ccd20..b19943e 100644
--- a/quire-server/src/mirror.rs
+++ b/quire-server/src/mirror.rs
@@ -1,4 +1,4 @@
-//! Server-side mirror: push a branch (and a version tag) to a remote on every push.
+//! Server-side mirror: push updated refs to a remote on every push.
//!
//! Triggered from the push event handler, independent of CI.
@@ -9,9 +9,8 @@ use crate::quire::Quire;
/// Mirror updated refs to a configured remote.
///
/// Reads `github.mirror-token` from global config for auth. For each updated
-/// ref, reads `.quire/config.fnl` at the new SHA to obtain `github.mirror` (URL)
-/// and `github.branch` (the ref that triggers mirroring). Skips refs that don't
-/// match the configured branch, and repos with no mirror URL set.
+/// ref, reads `.quire/config.fnl` at the new SHA to obtain the `github.mirror`
+/// URL. Skips repos with no mirror URL configured.
pub fn trigger(quire: &Quire, event: &PushEvent) {
let repo = match quire.repo(&event.repo) {
Ok(r) if r.exists() => r,
@@ -69,13 +68,8 @@ pub fn trigger(quire: &Quire, event: &PushEvent) {
continue;
};
- if push_ref.ref_name != repo_config.github.branch {
- continue;
- }
-
push_to_mirror(
&repo,
- &push_ref.new_sha,
&push_ref.ref_name,
&mirror_url,
mirror_token.as_deref(),
@@ -85,51 +79,22 @@ pub fn trigger(quire: &Quire, event: &PushEvent) {
fn push_to_mirror(
repo: &crate::quire::Repo,
- sha: &str,
ref_name: &str,
mirror_url: &str,
token: Option<&str>,
) {
- let tag = make_tag(sha);
-
- // Create the tag locally; ignore "already exists" errors.
- let tag_out = repo
- .git(&["tag", &tag, sha])
- .stdout(std::process::Stdio::null())
- .stderr(std::process::Stdio::piped())
- .output();
- match tag_out {
- Err(e) => {
- tracing::error!(sha, tag, error = %e, "mirror: failed to run git tag");
- return;
- }
- Ok(out) if !out.status.success() => {
- let stderr = String::from_utf8_lossy(&out.stderr);
- if !stderr.contains("already exists") {
- tracing::error!(sha, tag, %stderr, "mirror: git tag failed");
- return;
- }
- }
- Ok(_) => {}
- }
-
- // Force-push the branch and push the tag.
- // The `+` prefix in the branch refspec allows fast-forward and force pushes.
- let refspec_branch = format!("+{ref_name}:{ref_name}");
- let refspec_tag = format!("refs/tags/{tag}:refs/tags/{tag}");
- let mut cmd = repo.git(&[
- "push",
- "--porcelain",
- mirror_url,
- &refspec_branch,
- &refspec_tag,
- ]);
+ // Force-push the ref to the mirror. The `+` prefix allows rewrites.
+ let refspec = format!("+{ref_name}:{ref_name}");
+ let mut cmd = repo.git(&["push", "--porcelain", mirror_url, &refspec]);
// Pass the auth token via git config env vars so it never appears in argv.
if let Some(token) = token {
cmd.env("GIT_CONFIG_COUNT", "1")
.env("GIT_CONFIG_KEY_0", "http.extraHeader")
- .env("GIT_CONFIG_VALUE_0", format!("Authorization: Bearer {token}"));
+ .env(
+ "GIT_CONFIG_VALUE_0",
+ format!("Authorization: Bearer {token}"),
+ );
}
match cmd
@@ -138,48 +103,23 @@ fn push_to_mirror(
.output()
{
Ok(out) if out.status.success() => {
- tracing::info!(ref_name, tag, mirror_url, "mirror: push succeeded");
+ tracing::info!(ref_name, mirror_url, "mirror: push succeeded");
}
Ok(out) => {
tracing::error!(
ref_name,
- tag,
mirror_url,
stderr = %String::from_utf8_lossy(&out.stderr),
"mirror: push failed",
);
}
Err(e) => {
- tracing::error!(ref_name, mirror_url, error = %e, "mirror: failed to run git push");
+ tracing::error!(
+ ref_name,
+ mirror_url,
+ error = %e,
+ "mirror: failed to run git push",
+ );
}
}
}
-
-fn make_tag(sha: &str) -> String {
- let date = jiff::Timestamp::now().strftime("%Y-%m-%d").to_string();
- let sha8 = &sha[..sha.len().min(8)];
- format!("v{date}-{sha8}")
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn make_tag_format() {
- let sha = "abc12345def67890";
- let tag = make_tag(sha);
- assert!(tag.starts_with('v'), "tag should start with 'v': {tag}");
- // v<date>-<sha8>
- let parts: Vec<&str> = tag.splitn(2, '-').collect();
- assert_eq!(parts.len(), 2);
- assert_eq!(&tag[tag.len() - 8..], "abc12345");
- }
-
- #[test]
- fn make_tag_short_sha() {
- let sha = "abc";
- let tag = make_tag(sha);
- assert!(tag.ends_with("abc"), "short sha should be used as-is: {tag}");
- }
-}
diff --git a/quire-server/src/quire/mod.rs b/quire-server/src/quire/mod.rs
index 93493f7..7bc5384 100644
--- a/quire-server/src/quire/mod.rs
+++ b/quire-server/src/quire/mod.rs
@@ -43,8 +43,6 @@ pub struct GlobalConfig {
#[serde(rename_all = "kebab-case")]
pub struct GlobalGithubConfig {
/// Bearer token used to authenticate push access to the mirror remote.
- /// Exposed to the built-in `quire/mirror` job as the
- /// `"github/mirror-token"` secret.
#[serde(default)]
pub mirror_token: Option<SecretString>,
}
@@ -161,10 +159,7 @@ impl Repo {
/// Read and parse `.quire/config.fnl` at the given commit SHA.
///
/// Returns defaults if the file does not exist at that commit.
- pub fn repo_config(
- &self,
- sha: &str,
- ) -> AppResult<quire_core::ci::repo_config::RepoConfig> {
+ pub fn repo_config(&self, sha: &str) -> AppResult<quire_core::ci::repo_config::RepoConfig> {
let output = self
.git(&["show", &format!("{sha}:.quire/config.fnl")])
.stdout(std::process::Stdio::piped())