use crate::GitBaby; use crate::error::BabyError; use crate::remote::helper; use crate::remote::types::{ FetchRemoteCommitResult, RemoteOption, UpdateRemoteMirrorRequest, UpdateRemoteMirrorResult, }; async fn build_env( gitbaby: &GitBaby, ) -> Result<(std::path::PathBuf, crate::command::env::Env), BabyError> { let dir = gitbaby .facade .git_repo_dir() .await .map_err(crate::share::facade_error)?; let alternates = gitbaby .facade .git_alternate_object_directories() .await .map_err(crate::share::facade_error)?; Ok((dir, crate::share::build_env_safe(&alternates))) } impl GitBaby { pub async fn get_remote_address(&self, name: &str) -> Result, BabyError> { if name.is_empty() { return Err(BabyError::Custom( "remote name must not be empty".to_string(), )); } let (dir, env) = build_env(self).await?; let mut cmd = helper::build_get_url_cmd("gitbaby::remote", &dir, env, name)?; match cmd.run().await { Ok(output) => { if output.status.success() { let url = String::from_utf8_lossy(&output.stdout).trim().to_string(); return Ok(Some(url)); } let stderr = String::from_utf8_lossy(&output.stderr); if stderr.contains("No such remote") || stderr.contains("no such remote") { return Ok(None); } Err(BabyError::Custom(format!( "git remote get-url failed: {}", stderr.trim() ))) } Err(crate::command::error::CmdError::NonZeroExit { stderr, .. }) => { if stderr.contains("No such remote") || stderr.contains("no such remote") { Ok(None) } else { Err(BabyError::Custom(format!( "git remote get-url failed: {}", stderr.trim() ))) } } Err(e) => Err(BabyError::from(e)), } } pub async fn add_remote( &self, name: &str, url: &str, opts: RemoteOption, ) -> Result<(), BabyError> { if name.is_empty() { return Err(BabyError::Custom( "remote name must not be empty".to_string(), )); } if url.is_empty() { return Err(BabyError::InvalidRemoteUrl(url.to_string())); } let (dir, env) = build_env(self).await?; let mut cmd = helper::build_add_cmd("gitbaby::remote", &dir, env, name, url, opts)?; let output = cmd.run_soft().await.map_err(BabyError::from)?; if output.status.success() { return Ok(()); } Err(helper::classify_stderr_add( &String::from_utf8_lossy(&output.stderr), name, )) } pub async fn remove_remote(&self, name: &str) -> Result<(), BabyError> { if name.is_empty() { return Err(BabyError::Custom( "remote name must not be empty".to_string(), )); } let (dir, env) = build_env(self).await?; let mut cmd = helper::build_remove_cmd("gitbaby::remote", &dir, env, name)?; let output = cmd.run_soft().await.map_err(BabyError::from)?; if output.status.success() { return Ok(()); } Err(helper::classify_stderr_rm( &String::from_utf8_lossy(&output.stderr), name, )) } pub async fn fetch_remote_commit( &self, remote_repo_path: &str, commit_id: &str, ) -> Result { if remote_repo_path.is_empty() { return Err(BabyError::Custom( "remote repository path must not be empty".to_string(), )); } if commit_id.is_empty() { return Err(BabyError::Custom("commit id must not be empty".to_string())); } let (dir, env) = build_env(self).await?; let mut cmd = helper::build_fetch_cmd("gitbaby::remote", &dir, env, remote_repo_path, commit_id)?; let output = cmd.run_soft().await.map_err(BabyError::from)?; if output.status.success() { let _ = crate::cache::invalidation::flush_cache().await; return Ok(FetchRemoteCommitResult { fetched: true }); } let stderr = String::from_utf8_lossy(&output.stderr); if stderr.contains("not a git repository") { return Err(BabyError::NotARepository(remote_repo_path.to_string())); } Err(helper::classify_stderr_fetch(&stderr)) } pub async fn find_remote_repository(&self, url: &str) -> Result { if url.is_empty() { return Err(BabyError::InvalidRemoteUrl(url.to_string())); } let dir = self .facade .git_repo_dir() .await .map_err(crate::share::facade_error)?; let alternates = self .facade .git_alternate_object_directories() .await .map_err(crate::share::facade_error)?; let env = crate::share::build_env_safe(&alternates); let mut cmd = helper::build_ls_remote_head_cmd("gitbaby::remote", &dir, env, url)?; match cmd.run().await { Ok(output) => { if !output.status.success() { return Ok(false); } Ok(helper::parse_oid_tab_head(&output.stdout).is_some()) } Err(crate::command::error::CmdError::Spawn { .. }) => Ok(false), Err(crate::command::error::CmdError::NonZeroExit { .. }) => Ok(false), Err(crate::command::error::CmdError::Io { .. }) => Ok(false), Err(e) => Err(helper::classify_stderr_ls_remote(&format!("{:?}", e))), } } pub async fn find_remote_root_ref(&self, url: &str) -> Result, BabyError> { if url.is_empty() { return Err(BabyError::InvalidRemoteUrl(url.to_string())); } let dir = self .facade .git_repo_dir() .await .map_err(crate::share::facade_error)?; let alternates = self .facade .git_alternate_object_directories() .await .map_err(crate::share::facade_error)?; let env = crate::share::build_env_safe(&alternates); let mut cmd = helper::build_show_remote_cmd("gitbaby::remote", &dir, env, url)?; let output = match cmd.run().await { Ok(o) => o, Err(crate::command::error::CmdError::NonZeroExit { stderr, .. }) => { if helper::classify_stderr_show_remote(&stderr).is_none() { return Ok(None); } return Err(BabyError::Custom(format!( "git remote show failed: {}", stderr.trim() ))); } Err(e) => return Err(BabyError::from(e)), }; if !output.status.success() { return Ok(None); } match helper::parse_head_branch_line(&output.stdout) { Some(ref_name) if ref_name == "(unknown)" => Ok(None), Some(ref_name) => Ok(Some(ref_name)), None => Ok(None), } } pub async fn update_remote_mirror( &self, req: UpdateRemoteMirrorRequest, ) -> Result { if req.url.is_empty() { return Err(BabyError::InvalidRemoteUrl(req.url.clone())); } if req.refs.is_empty() { return Err(BabyError::Custom( "update_remote_mirror requires at least one ref".to_string(), )); } let (dir, env) = build_env(self).await?; let mut cmd = helper::build_push_mirror_cmd("gitbaby::remote", &dir, env, &req, req.refs.clone())?; let output = cmd.run_soft().await.map_err(BabyError::from)?; if output.status.success() { return Ok(UpdateRemoteMirrorResult::default()); } Err(helper::classify_stderr_push(&String::from_utf8_lossy( &output.stderr, ))) } }