//! remote usecase: remote/mirror operations. use crate::GitRepository; use crate::remote::impls::RemoteOps; use crate::remote::types::{ FetchResult, ImportResult, MirrorType, PushResult, RemoteAuth, RemoteConfig, }; use crate::validate; impl RemoteOps for GitRepository { fn remote_clone_import( &self, src: &str, _config: &RemoteConfig, ) -> Result { // SSRF / path-traversal guard: only allow http(s)/ssh remote URLs, // never local paths or file://. validate::allowed_remote_scheme(src)?; let repo = self.repo()?; let path = repo.path().to_path_buf(); // Import into the current bare repository via `git fetch` from the // remote (avoids a shell; src is a validated URL). crate::gitcli::run_remote( &path, &[ "fetch", "--quiet", src, "+refs/heads/*:refs/remotes/origin/*", ], &RemoteAuth::None, )?; let default_branch = crate::gitcli::run( &path, &["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], ) .unwrap_or_else(|_| "main".to_string()); let commit_count = crate::gitcli::run(&path, &["rev-list", "--count", "refs/remotes/origin/*"]) .ok() .and_then(|s| s.trim().parse::().ok()) .unwrap_or(0); tracing::info!(src = %src, commit_count, "remote clone/import completed"); Ok(ImportResult { default_branch, commit_count, }) } fn remote_fetch(&self, config: &RemoteConfig) -> Result { validate::allowed_remote_scheme(&config.url)?; let repo = self.repo()?; let path = repo.path().to_path_buf(); let refspec = config .refspec .as_deref() .unwrap_or("+refs/heads/*:refs/remotes/origin/*"); crate::gitcli::run_remote( &path, &["fetch", "--quiet", &config.url, refspec], &config.auth, )?; tracing::info!(url = %config.url, "remote fetch completed"); Ok(FetchResult { updated: vec![], new: vec![], }) } fn remote_push( &self, config: &RemoteConfig, refspec: Option<&str>, ) -> Result { validate::allowed_remote_scheme(&config.url)?; let repo = self.repo()?; let path = repo.path().to_path_buf(); let rs = refspec.unwrap_or("+refs/heads/*:refs/heads/*"); crate::gitcli::run_remote(&path, &["push", "--quiet", &config.url, rs], &config.auth)?; tracing::info!(url = %config.url, "remote push completed"); Ok(PushResult { pushed: vec![], rejected: vec![], }) } fn remote_set_mirror( &self, mirror_type: MirrorType, config: &RemoteConfig, ) -> Result<(), crate::Error> { validate::allowed_remote_scheme(&config.url)?; let repo = self.repo()?; let path = repo.path().to_path_buf(); let mirror_flag = match mirror_type { MirrorType::Pull => "--mirror=fetch", MirrorType::Push => "--mirror=push", }; crate::gitcli::run( &path, &["remote", "add", mirror_flag, "mirror", &config.url], )?; tracing::info!(url = %config.url, mirror = ?mirror_type, "mirror configured"); Ok(()) } fn remote_remove_remote(&self, name: &str) -> Result<(), crate::Error> { let repo = self.repo()?; let path = repo.path().to_path_buf(); crate::gitcli::run(&path, &["remote", "remove", name])?; tracing::info!(name, "remote removed"); Ok(()) } }