Something went wrong. Try again.
Mirror your Git repositories
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293use std::path::Path;use std::process::Command;
use anyhow::{Context as _, Result, bail};
/// Runs a Git command, returning its stdout.////// Stderr is inherited so that Git's progress output is visible to the user.fn git(args: &[&str]) -> Result<String> { let output = Command::new("git") .args(args) .stderr(std::process::Stdio::inherit()) .output() .context("failed to run `git`. Is Git installed?")?; if !output.status.success() { bail!("`git {}` exited with {}", args.join(" "), output.status); }
String::from_utf8(output.stdout).context("git output was not valid UTF-8")}
/// A local bare mirror of a repository, used as the staging area for pushes/// and bundles.pub struct MirrorRepo<'a> { path: &'a Path,}
impl<'a> MirrorRepo<'a> { /// Clones or updates a bare mirror of `source` at `path`. pub fn clone_or_update(source: &str, path: &'a Path) -> Result<Self> { if path.join("HEAD").exists() { let path_str = path_str(path)?; // Keep the remote URL in sync in case the source changed in the config. git(&["-C", path_str, "remote", "set-url", "origin", source])?; git(&["-C", path_str, "remote", "update", "--prune"])?; } else { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent).with_context(|| { format!("failed to create cache directory: {}", parent.display()) })?; } git(&["clone", "--mirror", source, path_str(path)?])?; }
Ok(Self { path }) }
/// Mirrors branches and tags to the given remote URL, force-updating and /// pruning refs that no longer exist in the source. /// /// We intentionally push only `refs/heads/*` and `refs/tags/*` rather than /// using `git push --mirror`, because mirrors of forges like GitHub /// contain read-only hidden refs (e.g. `refs/pull/*`) that destinations /// will reject. pub fn push_mirror(&self, url: &str) -> Result<()> { git(&[ "-C", path_str(self.path)?, "push", "--prune", "--force", url, "+refs/heads/*:refs/heads/*", "+refs/tags/*:refs/tags/*", ])?; Ok(()) }
/// Returns the output of `git for-each-ref`, which uniquely identifies /// the current state of all refs. pub fn refs_snapshot(&self) -> Result<String> { git(&["-C", path_str(self.path)?, "for-each-ref"]) }
/// Creates a bundle containing all refs at the given path. pub fn create_bundle(&self, bundle_path: &Path) -> Result<()> { git(&[ "-C", path_str(self.path)?, "bundle", "create", path_str(bundle_path)?, "--all", ])?; Ok(()) }}
fn path_str(path: &Path) -> Result<&str> { path.to_str() .with_context(|| format!("path is not valid UTF-8: {}", path.display()))}