use std::path::PathBuf; use std::str::FromStr; #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum GitService { UploadPack, ReceivePack, UploadArchive, } impl FromStr for GitService { type Err = (); fn from_str(s: &str) -> Result { match s { "upload-pack" => Ok(Self::UploadPack), "receive-pack" => Ok(Self::ReceivePack), "upload-archive" => Ok(Self::UploadArchive), _ => Err(()), } } } pub fn parse_git_command(cmd: &str) -> Option<(GitService, &str)> { let (svc, path) = match cmd.split_once(' ') { Some(("git-receive-pack", path)) => (GitService::ReceivePack, path), Some(("git-upload-pack", path)) => (GitService::UploadPack, path), Some(("git-upload-archive", path)) => (GitService::UploadArchive, path), _ => return None, }; Some((svc, strip_apostrophes(path))) } pub fn parse_repo_path(path: &str) -> Option<(&str, &str)> { let path = path.trim_matches('/'); let mut parts = path.splitn(2, '/'); match (parts.next(), parts.next()) { (Some(owner), Some(repo)) if !owner.is_empty() && !repo.is_empty() => Some((owner, repo)), _ => None, } } pub fn build_git_command(service: GitService, path: PathBuf) -> tokio::process::Command { let mut cmd = tokio::process::Command::new("git"); let cwd = match path.canonicalize() { Ok(p) => p, Err(e) => { tracing::debug!(error = %e, "path canonicalize failed, falling back to raw path"); path.clone() } }; cmd.current_dir(cwd); match service { GitService::UploadPack => { cmd.arg("upload-pack"); } GitService::ReceivePack => { cmd.arg("receive-pack"); } GitService::UploadArchive => { cmd.arg("upload-archive"); } } cmd.arg(".") .env("GIT_CONFIG_NOSYSTEM", "1") .env("GIT_NO_REPLACE_OBJECTS", "1"); #[cfg(unix)] { cmd.env("GIT_CONFIG_GLOBAL", "/dev/null") .env("GIT_CONFIG_SYSTEM", "/dev/null"); } #[cfg(windows)] { let nul = "NUL"; cmd.env("GIT_CONFIG_GLOBAL", nul) .env("GIT_CONFIG_SYSTEM", nul); } cmd } fn strip_apostrophes(s: &str) -> &str { s.trim_matches('\'') }