//! ref: composer/src/Composer/Util/Hg.php use crate::config::Config; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use crate::util::ProcessExecutor; use crate::util::Url; use shirabe_php_shim::{php_regex, preg_match, rawurlencode}; use std::sync::Mutex; static VERSION: Mutex>> = Mutex::new(None); #[derive(Debug)] pub struct Hg { io: std::rc::Rc>, config: std::rc::Rc>, process: std::rc::Rc>, } impl Hg { pub fn new( io: std::rc::Rc>, config: std::rc::Rc>, process: std::rc::Rc>, ) -> Self { Self { io, config, process, } } pub fn run_command( &self, command_callable: impl Fn(String) -> Vec, url: String, cwd: Option, ) -> anyhow::Result<()> { self.config.borrow_mut().prohibit_url_by_config( &url, Some(self.io.clone()), &indexmap::IndexMap::new(), )?; // Try as is let command = command_callable(url.clone()); let mut ignored_output = String::new(); if self .process .borrow_mut() .execute(&command, &mut ignored_output, cwd.as_deref())? == 0 { return Ok(()); } // Try with the authentication information available let matched = preg_match( php_regex!( r"{^(?Pssh|https?)://(?:(?P[^:@]+)(?::(?P[^:@]+))?@)?(?P[^/]+)(?P/.*)?}mi" ), &url, ); if let Some(matches) = matched && self .io .has_authentication(matches.name("host").unwrap_or("")) { let authenticated_url = if matches.name("proto") == Some("ssh") { let user = if let Some(u) = matches.name("user") { format!("{}@", rawurlencode(u)) } else { String::new() }; format!( "{}://{}{}{}", matches.name("proto").unwrap_or(""), user, matches.name("host").unwrap_or(""), matches.name("path").unwrap_or(""), ) } else { let auth = self .io .get_authentication(matches.name("host").unwrap_or("")); format!( "{}://{}:{}@{}{}", matches.name("proto").unwrap_or(""), rawurlencode( auth.get("username") .and_then(|s| s.as_deref()) .unwrap_or("") ), rawurlencode( auth.get("password") .and_then(|s| s.as_deref()) .unwrap_or("") ), matches.name("host").unwrap_or(""), matches.name("path").unwrap_or(""), ) }; let command = command_callable(authenticated_url); let mut ignored_output = String::new(); if self .process .borrow_mut() .execute(&command, &mut ignored_output, cwd.as_deref())? == 0 { return Ok(()); } let error = self.process.borrow().get_error_output().to_string(); return self.throw_exception(&format!("Failed to clone {}, \n\n{}", url, error), &url); } let error = format!( "The given URL ({}) does not match the required format (ssh|http(s)://(username:password@)example.com/path-to-repository)", url ); self.throw_exception(&format!("Failed to clone {}, \n\n{}", url, error), &url) } fn throw_exception(&self, message: &str, url: &str) -> anyhow::Result<()> { if Self::get_version(&self.process)?.is_none() { anyhow::bail!( "{}", Url::sanitize(format!( "Failed to clone {}, hg was not found, check that it is installed and in your PATH env.\n\n{}", url, self.process.borrow().get_error_output() )) ); } anyhow::bail!("{}", Url::sanitize(message.to_string())); } pub fn get_version( process: &std::rc::Rc>, ) -> anyhow::Result> { let mut version = VERSION.lock().unwrap(); if version.is_none() { *version = Some(None); let mut output = String::new(); if process.borrow_mut().execute( &["hg".to_string(), "--version".to_string()], &mut output, None, )? == 0 && let Some(matches) = preg_match( php_regex!(r"/^.+? (\d+(?:\.\d+)+)(?:\+.*?)?\)?\r?\n/"), &output, ) { *version = Some(matches.get(1).map(str::to_string)); } } Ok(version.clone().unwrap_or(None)) } }