use super::MarketplaceAddError;use std::fs;use std::path::Path;use std::path::PathBuf;use std::process::Command;pub(super) fn clone_git_source( url: &str, ref_name: Option<&str>, sparse_paths: &[String], destination: &Path,) -> Result<(), MarketplaceAddError> { let destination_string = destination.to_string_lossy().to_string(); if sparse_paths.is_empty() { run_git( &["clone", url, destination_string.as_str()], /*cwd*/ None, )?; if let Some(ref_name) = ref_name { run_git( &["checkout", ref_name], Some(Path::new(&destination_string)), )?; } return Ok(()); } run_git( &[ "clone", "--filter=blob:none", "--no-checkout", url, destination_string.as_str(), ], /*cwd*/ None, )?; let mut sparse_args = vec!["sparse-checkout", "set"]; sparse_args.extend(sparse_paths.iter().map(String::as_str)); run_git(&sparse_args, Some(destination))?; run_git(&["checkout", ref_name.unwrap_or("HEAD")], Some(destination))?; Ok(())}pub(super) fn safe_marketplace_dir_name( marketplace_name: &str,) -> Result<String, MarketplaceAddError> { let safe = marketplace_name .chars() .map(|ch| { if ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.') { ch } else { '-' } }) .collect::<String>(); let safe = safe.trim_matches('.').to_string(); if safe.is_empty() || safe == ".." { return Err(MarketplaceAddError::InvalidRequest(format!( "marketplace name '{marketplace_name}' cannot be used as an install directory" ))); } Ok(safe)}pub(super) fn ensure_marketplace_destination_is_inside_install_root( install_root: &Path, destination: &Path,) -> Result<(), MarketplaceAddError> { let install_root = install_root.canonicalize().map_err(|err| { MarketplaceAddError::Internal(format!( "failed to resolve marketplace install root {}: {err}", install_root.display() )) })?; let destination_parent = destination .parent() .ok_or_else(|| { MarketplaceAddError::Internal("marketplace destination has no parent".to_string()) })? .canonicalize() .map_err(|err| { MarketplaceAddError::Internal(format!( "failed to resolve marketplace destination parent {}: {err}", destination.display() )) })?; if !destination_parent.starts_with(&install_root) { return Err(MarketplaceAddError::InvalidRequest(format!( "marketplace destination {} is outside install root {}", destination.display(), install_root.display() ))); } Ok(())}pub(super) fn replace_marketplace_root( staged_root: &Path, destination: &Path,) -> std::io::Result<()> { if let Some(parent) = destination.parent() { fs::create_dir_all(parent)?; } fs::rename(staged_root, destination)}pub(super) fn marketplace_staging_root(install_root: &Path) -> PathBuf { install_root.join(".staging")}fn run_git(args: &[&str], cwd: Option<&Path>) -> Result<(), MarketplaceAddError> { let mut command = Command::new("git"); command.args(args); command.env("GIT_TERMINAL_PROMPT", "0"); if let Some(cwd) = cwd { command.current_dir(cwd); } let output = command.output().map_err(|err| { MarketplaceAddError::Internal(format!("failed to run git {}: {err}", args.join(" "))) })?; if output.status.success() { return Ok(()); } let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); Err(MarketplaceAddError::Internal(format!( "git {} failed with status {}\nstdout:\n{}\nstderr:\n{}", args.join(" "), output.status, stdout.trim(), stderr.trim() )))}