diff options
Diffstat (limited to 'crates/shirabe/src/repository/vcs')
| -rw-r--r-- | crates/shirabe/src/repository/vcs/forgejo_driver.rs | 46 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/fossil_driver.rs | 58 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs | 46 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/git_driver.rs | 108 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/github_driver.rs | 202 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/gitlab_driver.rs | 165 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/hg_driver.rs | 55 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/perforce_driver.rs | 1 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/svn_driver.rs | 132 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/vcs_driver.rs | 65 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/vcs_driver_interface.rs | 2 |
11 files changed, 569 insertions, 311 deletions
diff --git a/crates/shirabe/src/repository/vcs/forgejo_driver.rs b/crates/shirabe/src/repository/vcs/forgejo_driver.rs index 2efbfb7..179f2db 100644 --- a/crates/shirabe/src/repository/vcs/forgejo_driver.rs +++ b/crates/shirabe/src/repository/vcs/forgejo_driver.rs @@ -3,7 +3,7 @@ use crate::io::io_interface; use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ PhpMixed, RuntimeException, base64_decode, explode, extension_loaded, urlencode, }; @@ -15,6 +15,7 @@ use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; use crate::repository::vcs::git_driver::GitDriver; use crate::repository::vcs::vcs_driver::VcsDriverBase; +use crate::repository::vcs::vcs_driver_interface::VcsDriverInterface; use crate::util::forgejo::Forgejo; use crate::util::forgejo_repository_data::ForgejoRepositoryData; use crate::util::forgejo_url::ForgejoUrl; @@ -39,6 +40,7 @@ impl ForgejoDriver { "{}/{}/{}/{}", self.inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or(""), @@ -53,6 +55,7 @@ impl ForgejoDriver { c.set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -313,7 +316,7 @@ impl ForgejoDriver { identifier: &str, ) -> Result<Option<IndexMap<String, PhpMixed>>> { if let Some(ref mut git_driver) = self.git_driver { - return git_driver.inner.get_composer_information(identifier); + return git_driver.get_composer_information(identifier); } if !self.inner.info_cache.contains_key(identifier) { @@ -321,7 +324,12 @@ impl ForgejoDriver { if let Some(res) = self.inner.cache.as_ref().and_then(|c| c.read(identifier)) { JsonFile::parse_json(&res, None)? } else { - let c = self.inner.get_base_composer_information(identifier)?; + let file_content = self.get_file_content("composer.json", identifier)?; + let c = VcsDriverBase::finish_base_composer_information( + identifier, + file_content, + || self.get_change_date(identifier), + )?; if self.inner.should_cache(identifier) { if let Some(ref composer_map) = c { let encoded = JsonFile::encode_with_options( @@ -338,7 +346,10 @@ impl ForgejoDriver { c } } else { - self.inner.get_base_composer_information(identifier)? + let file_content = self.get_file_content("composer.json", identifier)?; + VcsDriverBase::finish_base_composer_information(identifier, file_content, || { + self.get_change_date(identifier) + })? }; let mut composer = composer; @@ -484,11 +495,11 @@ impl ForgejoDriver { } if !extension_loaded("openssl") { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "Skipping Forgejo driver for {} because the OpenSSL PHP extension is missing.", url - )), + ), true, io_interface::VERBOSE, ); @@ -510,7 +521,7 @@ impl ForgejoDriver { todo!("clone io for GitDriver setup"), self.inner.config.clone(), self.inner.http_downloader.clone(), - self.inner.process.clone(), + std::rc::Rc::clone(&self.inner.process), ), tags: None, branches: None, @@ -556,8 +567,11 @@ impl ForgejoDriver { let links = explode(",", &header); for link in links { - if let Some(m) = Preg::match_strict_groups(r#"{<(.+?)>; *rel="next"}"#, &link) { - if let Some(url) = m.get("1") { + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::match_strict_groups3(r#"{<(.+?)>; *rel="next"}"#, &link, Some(&mut m)) + .unwrap_or(false) + { + if let Some(url) = m.get(&CaptureKey::ByIndex(1)) { return Some(url.clone()); } } @@ -650,14 +664,10 @@ impl ForgejoDriver { Ok(()) => Ok(true), Err(e) => { self.git_driver = None; - self.inner.io.write_error( - PhpMixed::String(format!( - "<error>Failed to clone the {} repository, try running in interactive mode so that you can enter your Forgejo credentials</error>", - ssh_url - )), - true, - io_interface::NORMAL, - ); + self.inner.io.write_error3(&format!( + "<error>Failed to clone the {} repository, try running in interactive mode so that you can enter your Forgejo credentials</error>", + ssh_url + ), true, io_interface::NORMAL); Err(e) } } diff --git a/crates/shirabe/src/repository/vcs/fossil_driver.rs b/crates/shirabe/src/repository/vcs/fossil_driver.rs index a765c4c..f0c3468 100644 --- a/crates/shirabe/src/repository/vcs/fossil_driver.rs +++ b/crates/shirabe/src/repository/vcs/fossil_driver.rs @@ -29,9 +29,11 @@ impl FossilDriver { self.check_fossil()?; // Ensure we are allowed to use this URL by config. - self.inner - .config - .prohibit_url_by_config(&self.inner.url, &*self.inner.io)?; + self.inner.config.borrow_mut().prohibit_url_by_config( + &self.inner.url, + Some(&*self.inner.io), + &indexmap::IndexMap::new(), + )?; // Only if url points to a locally accessible directory, assume it's the checkout directory. // Otherwise, it should be something fossil can clone from. @@ -41,6 +43,7 @@ impl FossilDriver { let cache_repo_dir = self .inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or("") @@ -48,6 +51,7 @@ impl FossilDriver { let cache_vcs_dir = self .inner .config + .borrow_mut() .get("cache-vcs-dir") .as_string() .unwrap_or("") @@ -60,7 +64,7 @@ impl FossilDriver { .into()); } - let local_name = Preg::replace(r"{[^a-z0-9]}i", "-", self.inner.url.clone()); + let local_name = Preg::replace(r"{[^a-z0-9]}i", "-", &self.inner.url); self.repo_file = Some(format!("{}/{}.fossil", cache_repo_dir, local_name)); self.checkout_dir = format!("{}/{}/", cache_vcs_dir, local_name); @@ -75,7 +79,7 @@ impl FossilDriver { pub(crate) fn check_fossil(&self) -> anyhow::Result<()> { let mut ignored_output = String::new(); - if self.inner.process.execute( + if self.inner.process.borrow_mut().execute_args( &["fossil", "version"].map(|s| s.to_string()).to_vec(), &mut ignored_output, None, @@ -84,7 +88,7 @@ impl FossilDriver { return Err(RuntimeException { message: format!( "fossil was not found, check that it is installed and in your PATH env.\n\n{}", - self.inner.process.get_error_output() + self.inner.process.borrow().get_error_output() ), code: 0, } @@ -115,27 +119,23 @@ impl FossilDriver { // update the repo if it is a valid fossil repository if is_file(&repo_file) && is_dir(&self.checkout_dir) - && self.inner.process.execute( + && self.inner.process.borrow_mut().execute_args( &["fossil", "info"].map(|s| s.to_string()).to_vec(), &mut String::new(), Some(self.checkout_dir.clone()), ) == 0 { - if self.inner.process.execute( + if self.inner.process.borrow_mut().execute_args( &["fossil", "pull"].map(|s| s.to_string()).to_vec(), &mut String::new(), Some(self.checkout_dir.clone()), ) != 0 { - self.inner.io.write_error( - PhpMixed::String(format!( - "<error>Failed to update {}, package information from this repository may be outdated ({})</error>", - self.inner.url, - self.inner.process.get_error_output() - )), - true, - io_interface::NORMAL, - ); + self.inner.io.write_error3(&format!( + "<error>Failed to update {}, package information from this repository may be outdated ({})</error>", + self.inner.url, + self.inner.process.borrow().get_error_output() + ), true, io_interface::NORMAL); } } else { // clean up directory and do a fresh clone into it @@ -144,7 +144,7 @@ impl FossilDriver { fs.ensure_directory_exists(&self.checkout_dir)?; let mut output = String::new(); - if self.inner.process.execute( + if self.inner.process.borrow_mut().execute_args( &["fossil", "clone", "--", &self.inner.url, &repo_file] .map(|s| s.to_string()) .to_vec(), @@ -152,7 +152,7 @@ impl FossilDriver { None, ) != 0 { - let output = self.inner.process.get_error_output(); + let output = self.inner.process.borrow().get_error_output(); return Err(RuntimeException { message: format!( "Failed to clone {} to repository {}\n\n{}", @@ -163,7 +163,7 @@ impl FossilDriver { .into()); } - if self.inner.process.execute( + if self.inner.process.borrow_mut().execute_args( &["fossil", "open", "--nested", "--", &repo_file] .map(|s| s.to_string()) .to_vec(), @@ -171,7 +171,7 @@ impl FossilDriver { Some(self.checkout_dir.clone()), ) != 0 { - let output = self.inner.process.get_error_output(); + let output = self.inner.process.borrow().get_error_output(); return Err(RuntimeException { message: format!( "Failed to open repository {} in {}\n\n{}", @@ -222,7 +222,7 @@ impl FossilDriver { } let mut content = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["fossil", "cat", "-r", identifier, "--", file] .map(|s| s.to_string()) .to_vec(), @@ -239,7 +239,7 @@ impl FossilDriver { pub fn get_change_date(&self, _identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> { let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["fossil", "finfo", "-b", "-n", "1", "composer.json"] .map(|s| s.to_string()) .to_vec(), @@ -257,12 +257,12 @@ impl FossilDriver { if self.tags.is_none() { let mut tags: IndexMap<String, String> = IndexMap::new(); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["fossil", "tag", "list"].map(|s| s.to_string()).to_vec(), &mut output, Some(self.checkout_dir.clone()), ); - for tag in self.inner.process.split_lines(&output) { + for tag in self.inner.process.borrow().split_lines(&output) { tags.insert(tag.clone(), tag); } self.tags = Some(tags); @@ -274,13 +274,13 @@ impl FossilDriver { if self.branches.is_none() { let mut branches: IndexMap<String, String> = IndexMap::new(); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["fossil", "branch", "list"].map(|s| s.to_string()).to_vec(), &mut output, Some(self.checkout_dir.clone()), ); - for branch in self.inner.process.split_lines(&output) { - let branch = Preg::replace(r"/^\*/", "", branch.trim().to_string()); + for branch in self.inner.process.borrow().split_lines(&output) { + let branch = Preg::replace(r"/^\*/", "", &branch.trim()); let branch = branch.trim().to_string(); branches.insert(branch.clone(), branch); } @@ -312,7 +312,7 @@ impl FossilDriver { let process = ProcessExecutor::new(io); let mut output = String::new(); - if process.execute( + if process.execute_args( &["fossil", "info"].map(|s| s.to_string()).to_vec(), &mut output, Some(url), diff --git a/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs b/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs index 05e7c61..689c0e8 100644 --- a/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs +++ b/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs @@ -4,7 +4,7 @@ use crate::io::io_interface; use anyhow::Result; use chrono::{DateTime, Utc}; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ InvalidArgumentException, LogicException, PhpMixed, RuntimeException, array_key_exists, array_search_mixed, extension_loaded, http_build_query_mixed, implode, in_array, is_array, @@ -58,11 +58,14 @@ pub struct GitBitbucketDriver { impl GitBitbucketDriver { /// @inheritDoc pub fn initialize(&mut self) -> Result<()> { - let matched = Preg::is_match_strict_groups( + let mut m: indexmap::IndexMap<CaptureKey, String> = indexmap::IndexMap::new(); + if !Preg::is_match_strict_groups3( r"#^https?://bitbucket\.org/([^/]+)/([^/]+?)(?:\.git|/?)?$#i", &self.inner.url, - ); - if matched.is_none() { + Some(&mut m), + ) + .unwrap_or(false) + { return Err(InvalidArgumentException { message: sprintf( "The Bitbucket repository URL %s is invalid. It must be the HTTPS URL of a Bitbucket repository.", @@ -72,10 +75,9 @@ impl GitBitbucketDriver { } .into()); } - let m = matched.unwrap(); - self.owner = m.get(1).cloned().unwrap_or_default(); - self.repository = m.get(2).cloned().unwrap_or_default(); + self.owner = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); + self.repository = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); self.inner.origin_url = "bitbucket.org".to_string(); self.inner.cache = Some(Cache::new( &*self.inner.io, @@ -84,6 +86,7 @@ impl GitBitbucketDriver { &[ self.inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or("") @@ -98,6 +101,7 @@ impl GitBitbucketDriver { self.inner.cache.as_mut().unwrap().set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -236,7 +240,12 @@ impl GitBitbucketDriver { } { // composer already set above } else { - composer = self.inner.get_base_composer_information(identifier)?; + let file_content = self.get_file_content("composer.json", identifier)?; + composer = VcsDriverBase::finish_base_composer_information( + identifier, + file_content, + || self.get_change_date(identifier), + )?; if self.inner.should_cache(identifier) { self.inner.cache.as_ref().unwrap().write( @@ -664,16 +673,17 @@ impl GitBitbucketDriver { url: &str, fetching_repo_data: bool, ) -> Result<Response> { - match self.inner.get_contents(url, false) { + match self.inner.get_contents(url) { Ok(r) => Ok(r), Err(e) => { // TODO(phase-b): only handle TransportException - let bitbucket_util = Bitbucket::new( - &*self.inner.io, - &self.inner.config, - Some(self.inner.process.clone()), - Some(self.inner.http_downloader.clone()), - ); + let mut bitbucket_util = Bitbucket::new( + self.inner.io.clone_box(), + std::rc::Rc::clone(&self.inner.config), + Some(std::rc::Rc::clone(&self.inner.process)), + Some(std::rc::Rc::clone(&self.inner.http_downloader)), + None, + )?; if let Some(te) = e.downcast_ref::<TransportException>() { let code = te.get_code(); @@ -693,7 +703,7 @@ impl GitBitbucketDriver { if !self.inner.io.has_authentication(&self.inner.origin_url) && bitbucket_util.authorize_oauth(&self.inner.origin_url) { - return self.inner.get_contents(url, false); + return self.inner.get_contents(url); } if !self.inner.io.is_interactive() && fetching_repo_data { @@ -833,7 +843,9 @@ impl GitBitbucketDriver { if !Preg::is_match( r"#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i", url, - ) { + ) + .unwrap_or(false) + { return false; } diff --git a/crates/shirabe/src/repository/vcs/git_driver.rs b/crates/shirabe/src/repository/vcs/git_driver.rs index 33474b1..07836bf 100644 --- a/crates/shirabe/src/repository/vcs/git_driver.rs +++ b/crates/shirabe/src/repository/vcs/git_driver.rs @@ -4,7 +4,7 @@ use crate::io::io_interface; use chrono::TimeZone; use chrono::{DateTime, Utc}; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ InvalidArgumentException, RuntimeException, dirname, is_dir, is_writable, realpath, sys_get_temp_dir, @@ -32,7 +32,7 @@ impl GitDriver { pub fn initialize(&mut self) -> anyhow::Result<()> { let cache_url; if Filesystem::is_local_path(&self.inner.url) { - self.inner.url = Preg::replace(r"{[\\/]\.git/?$}", "", self.inner.url.clone())?; + self.inner.url = Preg::replace(r"{[\\/]\.git/?$}", "", &self.inner.url)?; if !is_dir(&self.inner.url) { return Err(RuntimeException { message: format!( @@ -49,6 +49,7 @@ impl GitDriver { let cache_vcs_dir = self .inner .config + .borrow_mut() .get("cache-vcs-dir") .as_string() .unwrap_or("") @@ -97,9 +98,9 @@ impl GitDriver { let git_util = GitUtil::new( &*self.inner.io, - &self.inner.config, - &self.inner.process, - &Filesystem::new(None), + std::rc::Rc::clone(&self.inner.config), + std::rc::Rc::clone(&self.inner.process), + std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new(None))), ); if !git_util.sync_mirror(&self.inner.url, &self.repo_dir)? { if !is_dir(&self.repo_dir) { @@ -112,14 +113,10 @@ impl GitDriver { } .into()); } - self.inner.io.write_error( - shirabe_php_shim::PhpMixed::String(format!( - "<error>Failed to update {}, package information from this repository may be outdated</error>", - self.inner.url - )), - true, - io_interface::NORMAL, - ); + self.inner.io.write_error3(shirabe_php_shim::PhpMixed::String(format!( + "<error>Failed to update {}, package information from this repository may be outdated</error>", + self.inner.url + )), true, io_interface::NORMAL); } cache_url = self.inner.url.clone(); @@ -131,6 +128,7 @@ impl GitDriver { let cache_repo_dir = self .inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or("") @@ -147,6 +145,7 @@ impl GitDriver { c.set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -162,9 +161,9 @@ impl GitDriver { let git_util = GitUtil::new( &*self.inner.io, - &self.inner.config, - &self.inner.process, - &Filesystem::new(None), + std::rc::Rc::clone(&self.inner.config), + std::rc::Rc::clone(&self.inner.process), + std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new(None))), ); if !Filesystem::is_local_path(&self.inner.url) { let default_branch = @@ -176,7 +175,7 @@ impl GitDriver { } let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &[ "git".to_string(), "branch".to_string(), @@ -185,12 +184,15 @@ impl GitDriver { &mut output, Some(self.repo_dir.clone()), ); - let branches = self.inner.process.split_lines(&output); + let branches = self.inner.process.borrow().split_lines(&output); if !branches.contains(&"* master".to_string()) { for branch in &branches { if !branch.is_empty() { - if let Some(caps) = Preg::match_strict_groups(r"{^\* +(\S+)}", branch) { - if let Some(name) = caps.get("1") { + let mut caps: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::match_strict_groups3(r"{^\* +(\S+)}", branch, Some(&mut caps)) + .unwrap_or(false) + { + if let Some(name) = caps.get(&CaptureKey::ByIndex(1)) { self.root_identifier = Some(name.clone()); break; } @@ -236,7 +238,7 @@ impl GitDriver { } let mut content = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &[ "git".to_string(), "show".to_string(), @@ -274,9 +276,11 @@ impl GitDriver { ], ); let mut output = String::new(); - self.inner - .process - .execute(&command, &mut output, Some(self.repo_dir.clone())); + self.inner.process.borrow_mut().execute_args( + &command, + &mut output, + Some(self.repo_dir.clone()), + ); let timestamp_str = GitUtil::parse_rev_list_output(&output, &self.inner.process); let timestamp: i64 = timestamp_str.trim().parse().unwrap_or(0); @@ -288,7 +292,7 @@ impl GitDriver { self.tags = Some(IndexMap::new()); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &[ "git".to_string(), "show-ref".to_string(), @@ -298,13 +302,20 @@ impl GitDriver { &mut output, Some(self.repo_dir.clone()), ); - for tag in self.inner.process.split_lines(&output) { + for tag in self.inner.process.borrow().split_lines(&output) { if !tag.is_empty() { - if let Some(caps) = Preg::match_strict_groups( + let mut caps: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::match_strict_groups3( r"{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}", &tag, - ) { - if let (Some(hash), Some(name)) = (caps.get("1"), caps.get("2")) { + Some(&mut caps), + ) + .unwrap_or(false) + { + if let (Some(hash), Some(name)) = ( + caps.get(&CaptureKey::ByIndex(1)), + caps.get(&CaptureKey::ByIndex(2)), + ) { self.tags .as_mut() .unwrap() @@ -323,7 +334,7 @@ impl GitDriver { let mut branches = IndexMap::new(); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &[ "git".to_string(), "branch".to_string(), @@ -334,15 +345,22 @@ impl GitDriver { &mut output, Some(self.repo_dir.clone()), ); - for branch in self.inner.process.split_lines(&output) { + for branch in self.inner.process.borrow().split_lines(&output) { if !branch.is_empty() && !Preg::is_match(r"{^ *[^/]+/HEAD }", &branch).unwrap_or(false) { - if let Some(caps) = Preg::match_strict_groups( + let mut caps: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::match_strict_groups3( r"{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}", &branch, - ) { - if let (Some(name), Some(hash)) = (caps.get("1"), caps.get("2")) { + Some(&mut caps), + ) + .unwrap_or(false) + { + if let (Some(name), Some(hash)) = ( + caps.get(&CaptureKey::ByIndex(1)), + caps.get(&CaptureKey::ByIndex(2)), + ) { if !name.starts_with('-') { branches.insert(name.clone(), hash.clone()); } @@ -378,9 +396,9 @@ impl GitDriver { return Ok(false); } - let process = ProcessExecutor::new(io); + let process = std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(io))); let mut output = String::new(); - if process.execute( + if process.borrow_mut().execute_args( &["git".to_string(), "tag".to_string()], &mut output, Some(url.clone()), @@ -388,15 +406,27 @@ impl GitDriver { { return Ok(true); } - GitUtil::check_for_repo_ownership_error(&process.get_error_output(), &url); + GitUtil::check_for_repo_ownership_error(&process.borrow().get_error_output(), &url); } if !deep { return Ok(false); } - let process = ProcessExecutor::new(io); - let git_util = GitUtil::new(io, _config, &process, &Filesystem::new(None)); + let process = std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(io))); + // TODO(phase-b): supports() takes &Config; GitUtil now needs Rc<RefCell<Config>>. + // Skipping clean Rc construction since we cannot reconstruct one from a borrowed &Config. + let _ = _config; + return Err(anyhow::anyhow!( + "GitDriver::supports requires Rc<RefCell<Config>>: not yet ported" + )); + #[allow(unreachable_code)] + let git_util = GitUtil::new( + io.clone_box(), + todo!(), + std::rc::Rc::clone(&process), + std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new(None))), + ); GitUtil::clean_env(&process); let result = git_util.run_commands( diff --git a/crates/shirabe/src/repository/vcs/github_driver.rs b/crates/shirabe/src/repository/vcs/github_driver.rs index 7bf15bf..93bfcdb 100644 --- a/crates/shirabe/src/repository/vcs/github_driver.rs +++ b/crates/shirabe/src/repository/vcs/github_driver.rs @@ -4,7 +4,7 @@ use crate::io::io_interface; use anyhow::Result; use chrono::{DateTime, Utc}; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ InvalidArgumentException, PhpMixed, RuntimeException, array_diff, array_key_exists, array_map, array_search_mixed, base64_decode, basename, count, empty, explode, extension_loaded, in_array, @@ -18,6 +18,7 @@ use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; use crate::repository::vcs::git_driver::GitDriver; use crate::repository::vcs::vcs_driver::VcsDriverBase; +use crate::repository::vcs::vcs_driver_interface::VcsDriverInterface; use crate::util::github::GitHub; use crate::util::http::response::Response; @@ -45,31 +46,43 @@ pub struct GitHubDriver { impl GitHubDriver { pub fn initialize(&mut self) -> Result<()> { - let match_ = match Preg::is_match_strict_groups( + let mut match_: IndexMap<CaptureKey, String> = IndexMap::new(); + if !Preg::is_match_strict_groups3( r"#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#", &self.inner.url, - ) { - Some(m) => m, - None => { - return Err(InvalidArgumentException { - message: sprintf( - "The GitHub repository URL %s is invalid.", - &[PhpMixed::String(self.inner.url.clone())], - ), - code: 0, - } - .into()); + Some(&mut match_), + ) + .unwrap_or(false) + { + return Err(InvalidArgumentException { + message: sprintf( + "The GitHub repository URL %s is invalid.", + &[PhpMixed::String(self.inner.url.clone())], + ), + code: 0, } - }; + .into()); + } - self.owner = match_.get(3).cloned().unwrap_or_default(); - self.repository = match_.get(4).cloned().unwrap_or_default(); + self.owner = match_ + .get(&CaptureKey::ByIndex(3)) + .cloned() + .unwrap_or_default(); + self.repository = match_ + .get(&CaptureKey::ByIndex(4)) + .cloned() + .unwrap_or_default(); self.inner.origin_url = strtolower( &match_ - .get(1) + .get(&CaptureKey::ByIndex(1)) .cloned() .filter(|s| !s.is_empty()) - .unwrap_or_else(|| match_.get(2).cloned().unwrap_or_default()), + .unwrap_or_else(|| { + match_ + .get(&CaptureKey::ByIndex(2)) + .cloned() + .unwrap_or_default() + }), ); if self.inner.origin_url == "www.github.com" { self.inner.origin_url = "github.com".to_string(); @@ -80,6 +93,7 @@ impl GitHubDriver { "{}/{}/{}/{}", self.inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or(""), @@ -95,6 +109,7 @@ impl GitHubDriver { c.set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -111,7 +126,13 @@ impl GitHubDriver { self.allow_git_fallback = false; } - if self.inner.config.get("use-github-api").as_bool() == Some(false) + if self + .inner + .config + .borrow_mut() + .get("use-github-api") + .as_bool() + == Some(false) || self .inner .repo_config @@ -230,7 +251,12 @@ impl GitHubDriver { .unwrap_or_default(); JsonFile::parse_json(&res, None)? } else { - let composer = self.inner.get_base_composer_information(identifier)?; + let file_content = self.get_file_content("composer.json", identifier)?; + let composer = VcsDriverBase::finish_base_composer_information( + identifier, + file_content, + || self.get_change_date(identifier), + )?; if self.inner.should_cache(identifier) { if let Some(ref composer_map) = composer { @@ -384,7 +410,7 @@ impl GitHubDriver { ] { let mut options: IndexMap<String, PhpMixed> = IndexMap::new(); options.insert("retry-auth-failure".to_string(), PhpMixed::Bool(false)); - let response = self.inner.http_downloader.get( + let response = self.inner.http_downloader.borrow_mut().get( file_url, &PhpMixed::Array(options.into_iter().map(|(k, v)| (k, Box::new(v))).collect()), ); @@ -436,20 +462,26 @@ impl GitHubDriver { let mut result: Vec<IndexMap<String, PhpMixed>> = vec![]; let mut key: Option<String> = None; - for line in Preg::split(r"{\r?\n}", &funding) { + for line in Preg::split(r"{\r?\n}", &funding).unwrap_or_default() { let line = trim(&line, None); - if let Some(m) = Preg::is_match_strict_groups(r"{^(\w+)\s*:\s*(.+)$}", &line) { - let g1 = m.get(1).cloned().unwrap_or_default(); - let g2 = m.get(2).cloned().unwrap_or_default(); + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3(r"{^(\w+)\s*:\s*(.+)$}", &line, Some(&mut m)) + .unwrap_or(false) + { + let g1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); + let g2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); if g2 == "[" { key = Some(g1); continue; } - if let Some(m2) = Preg::is_match_strict_groups(r"{^\[(.*?)\](?:\s*#.*)?$}", &g2) { - let inner = m2.get(1).cloned().unwrap_or_default(); + let mut m2: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3(r"{^\[(.*?)\](?:\s*#.*)?$}", &g2, Some(&mut m2)) + .unwrap_or(false) + { + let inner = m2.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); for item in array_map( |s: &String| trim(s, None), - &Preg::split(r#"{[\'\"]?\s*,\s*[\'\"]?}"#, &inner), + &Preg::split(r#"{[\'\"]?\s*,\s*[\'\"]?}"#, &inner).unwrap_or_default(), ) { let mut entry = IndexMap::new(); entry.insert("type".to_string(), PhpMixed::String(g1.clone())); @@ -459,30 +491,40 @@ impl GitHubDriver { ); result.push(entry); } - } else if let Some(m2) = - Preg::is_match_strict_groups(r"{^([^#].*?)(?:\s+#.*)?$}", &g2) + } else if Preg::is_match_strict_groups3( + r"{^([^#].*?)(?:\s+#.*)?$}", + &g2, + Some(&mut m2), + ) + .unwrap_or(false) { let mut entry = IndexMap::new(); entry.insert("type".to_string(), PhpMixed::String(g1.clone())); entry.insert( "url".to_string(), PhpMixed::String(trim( - &m2.get(1).cloned().unwrap_or_default(), + &m2.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(), Some("\"' "), )), ); result.push(entry); } key = None; - } else if let Some(m) = Preg::is_match_strict_groups(r"{^(\w+)\s*:\s*#\s*$}", &line) { - key = Some(m.get(1).cloned().unwrap_or_default()); - } else if key.is_some() - && (Preg::is_match_strict_groups(r"{^-\s*(.+)(?:\s+#.*)?$}", &line).is_some() - || Preg::is_match_strict_groups(r"{^(.+),(?:\s*#.*)?$}", &line).is_some()) + } else if Preg::is_match_strict_groups3(r"{^(\w+)\s*:\s*#\s*$}", &line, Some(&mut m)) + .unwrap_or(false) { - let m = Preg::is_match_strict_groups(r"{^-\s*(.+)(?:\s+#.*)?$}", &line) - .or_else(|| Preg::is_match_strict_groups(r"{^(.+),(?:\s*#.*)?$}", &line)) - .unwrap(); + key = Some(m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default()); + } else if key.is_some() && { + let mut tmp: IndexMap<CaptureKey, String> = IndexMap::new(); + Preg::is_match_strict_groups3(r"{^-\s*(.+)(?:\s+#.*)?$}", &line, Some(&mut m)) + .unwrap_or(false) + || Preg::is_match_strict_groups3(r"{^(.+),(?:\s*#.*)?$}", &line, Some(&mut tmp)) + .unwrap_or(false) + && { + m = tmp; + true + } + } { let mut entry = IndexMap::new(); entry.insert( "type".to_string(), @@ -490,7 +532,10 @@ impl GitHubDriver { ); entry.insert( "url".to_string(), - PhpMixed::String(trim(&m.get(1).cloned().unwrap_or_default(), Some("\"' "))), + PhpMixed::String(trim( + &m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(), + Some("\"' "), + )), ); result.push(entry); } else if key.is_some() && line == "]" { @@ -623,11 +668,11 @@ impl GitHubDriver { continue; } - self.inner.io.write_error( - PhpMixed::String(format!( + self.inner.io.write_error3( + &format!( "<warning>Funding URL {} not in a supported format.</warning>", item_url - )), + ), true, io_interface::NORMAL, ); @@ -874,21 +919,31 @@ impl GitHubDriver { } pub fn supports(io: &dyn IOInterface, config: &Config, url: &str, _deep: bool) -> bool { - let matches = match Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if !Preg::is_match_strict_groups3( r"#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#", url, - ) { - Some(m) => m, - None => return false, - }; + Some(&mut matches), + ) + .unwrap_or(false) + { + return false; + } let origin_url = matches - .get(2) + .get(&CaptureKey::ByIndex(2)) .cloned() .filter(|s| !s.is_empty()) - .unwrap_or_else(|| matches.get(3).cloned().unwrap_or_default()); + .unwrap_or_else(|| { + matches + .get(&CaptureKey::ByIndex(3)) + .cloned() + .unwrap_or_default() + }); if !in_array( - PhpMixed::String(strtolower(&Preg::replace(r"{^www\.}i", "", origin_url))), + PhpMixed::String(strtolower( + &Preg::replace(r"{^www\.}i", "", &origin_url).unwrap_or_default(), + )), &config.get("github-domains"), false, ) { @@ -896,11 +951,11 @@ impl GitHubDriver { } if !extension_loaded("openssl") { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "Skipping GitHub driver for {} because the OpenSSL PHP extension is missing.", url - )), + ), true, io_interface::VERBOSE, ); @@ -945,11 +1000,11 @@ impl GitHubDriver { Ok(r) => Ok(r), Err(e) => { let mut git_hub_util = GitHub::new( - self.inner.io.as_ref(), - &self.inner.config, - &self.inner.process, - &self.inner.http_downloader, - ); + self.inner.io.clone_box(), + std::rc::Rc::clone(&self.inner.config), + Some(std::rc::Rc::clone(&self.inner.process)), + Some(std::rc::Rc::clone(&self.inner.http_downloader)), + )?; match e.code { 401 | 404 => { @@ -1057,11 +1112,11 @@ impl GitHubDriver { if !self.inner.io.has_authentication(&self.inner.origin_url) { if !self.inner.io.is_interactive() { - self.inner.io.write_error( - PhpMixed::String(format!( + self.inner.io.write_error3( + &format!( "<error>GitHub API limit exhausted. Failed to get metadata for the {} repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit</error>", self.inner.url - )), + ), true, io_interface::NORMAL, ); @@ -1083,14 +1138,14 @@ impl GitHubDriver { let rate_limit = git_hub_util.get_rate_limit( e.get_headers().map(|h| h.as_slice()).unwrap_or(&[]), ); - self.inner.io.write_error( - PhpMixed::String(sprintf( + self.inner.io.write_error3( + &sprintf( "<error>GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests</error>", &[ rate_limit.get("limit").cloned().unwrap_or(PhpMixed::Null), rate_limit.get("reset").cloned().unwrap_or(PhpMixed::Null), ], - )), + ), true, io_interface::NORMAL, ); @@ -1206,11 +1261,11 @@ impl GitHubDriver { Err(setup_err) => { self.git_driver = None; - self.inner.io.write_error( - PhpMixed::String(format!( + self.inner.io.write_error3( + &format!( "<error>Failed to clone the {} repository, try running in interactive mode so that you can enter your GitHub credentials</error>", self.generate_ssh_url() - )), + ), true, io_interface::NORMAL, ); @@ -1233,8 +1288,8 @@ impl GitHubDriver { repo_config, self.inner.io.clone(), self.inner.config.clone(), - self.inner.http_downloader.clone(), - self.inner.process.clone(), + std::rc::Rc::clone(&self.inner.http_downloader), + std::rc::Rc::clone(&self.inner.process), ); git_driver.initialize()?; self.git_driver = Some(git_driver); @@ -1249,8 +1304,11 @@ impl GitHubDriver { let links = explode(",", &header); for link in &links { - if let Some(m) = Preg::is_match_strict_groups(r#"{<(.+?)>; *rel="next"}"#, link) { - return Some(m.get(1).cloned().unwrap_or_default()); + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3(r#"{<(.+?)>; *rel="next"}"#, link, Some(&mut m)) + .unwrap_or(false) + { + return Some(m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default()); } } diff --git a/crates/shirabe/src/repository/vcs/gitlab_driver.rs b/crates/shirabe/src/repository/vcs/gitlab_driver.rs index dea1bf6..e00bbf8 100644 --- a/crates/shirabe/src/repository/vcs/gitlab_driver.rs +++ b/crates/shirabe/src/repository/vcs/gitlab_driver.rs @@ -4,7 +4,7 @@ use crate::io::io_interface; use anyhow::Result; use chrono::{DateTime, Utc}; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ InvalidArgumentException, LogicException, PhpMixed, RuntimeException, array_search_mixed, array_shift, ctype_alnum, empty, explode, extension_loaded, implode, in_array, is_array, @@ -18,6 +18,7 @@ use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; use crate::repository::vcs::git_driver::GitDriver; use crate::repository::vcs::vcs_driver::VcsDriverBase; +use crate::repository::vcs::vcs_driver_interface::VcsDriverInterface; use crate::util::gitlab::GitLab; use crate::util::http::response::Response; use crate::util::http_downloader::HttpDownloader; @@ -57,30 +58,43 @@ impl GitLabDriver { /// /// SSH urls use https by default. Set "secure-http": false on the repository config to use http instead. pub fn initialize(&mut self) -> Result<()> { - let match_ = match Preg::is_match_strict_groups(Self::URL_REGEX, &self.inner.url) { - Some(m) => m, - None => { - return Err(InvalidArgumentException { - message: sprintf( - "The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.", - &[PhpMixed::String(self.inner.url.clone())], - ), - code: 0, - } - .into()); + let mut match_: IndexMap<CaptureKey, String> = IndexMap::new(); + if !Preg::is_match_strict_groups3(Self::URL_REGEX, &self.inner.url, Some(&mut match_)) + .unwrap_or(false) + { + return Err(InvalidArgumentException { + message: sprintf( + "The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.", + &[PhpMixed::String(self.inner.url.clone())], + ), + code: 0, } - }; + .into()); + } let guessed_domain = match_ - .get("domain") + .get(&CaptureKey::ByName("domain".to_string())) .cloned() .filter(|s| !s.is_empty()) - .unwrap_or_else(|| match_.get("domain2").cloned().unwrap_or_default()); - let configured_domains = self.inner.config.get("gitlab-domains"); - let mut url_parts: Vec<String> = - explode("/", &match_.get("parts").cloned().unwrap_or_default()); + .unwrap_or_else(|| { + match_ + .get(&CaptureKey::ByName("domain2".to_string())) + .cloned() + .unwrap_or_default() + }); + let configured_domains = self.inner.config.borrow_mut().get("gitlab-domains"); + let mut url_parts: Vec<String> = explode( + "/", + &match_ + .get(&CaptureKey::ByName("parts".to_string())) + .cloned() + .unwrap_or_default(), + ); - let scheme_match = match_.get("scheme").cloned().unwrap_or_default(); + let scheme_match = match_ + .get(&CaptureKey::ByName("scheme".to_string())) + .cloned() + .unwrap_or_default(); self.scheme = if in_array( PhpMixed::String(scheme_match.clone()), &PhpMixed::List(vec![ @@ -101,7 +115,7 @@ impl GitLabDriver { } else { "https".to_string() }; - let port = match_.get("port").cloned(); + let port = match_.get(&CaptureKey::ByName("port".to_string())).cloned(); let origin = Self::determine_origin( &configured_domains, guessed_domain, @@ -123,7 +137,7 @@ impl GitLabDriver { }; self.inner.origin_url = origin; - let protocol_value = self.inner.config.get("gitlab-protocol"); + let protocol_value = self.inner.config.borrow_mut().get("gitlab-protocol"); if let Some(protocol) = protocol_value .as_string() .filter(|_| is_string(&protocol_value)) @@ -161,8 +175,12 @@ impl GitLabDriver { self.repository = Preg::replace( r"#(\.git)$#", "", - match_.get("repo").cloned().unwrap_or_default(), - ); + &match_ + .get(&CaptureKey::ByName("repo".to_string())) + .cloned() + .unwrap_or_default(), + ) + .unwrap_or_default(); self.inner.cache = Some(Cache::new( self.inner.io.as_ref(), @@ -170,6 +188,7 @@ impl GitLabDriver { "{}/{}/{}/{}", self.inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or(""), @@ -185,6 +204,7 @@ impl GitLabDriver { c.set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -200,7 +220,10 @@ impl GitLabDriver { /// Mainly useful for tests. /// /// @internal - pub fn set_http_downloader(&mut self, http_downloader: HttpDownloader) { + pub fn set_http_downloader( + &mut self, + http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, + ) { self.inner.http_downloader = http_downloader; } @@ -229,7 +252,12 @@ impl GitLabDriver { .unwrap_or_default(); JsonFile::parse_json(&res, None)? } else { - let composer = self.inner.get_base_composer_information(identifier)?; + let file_content = self.get_file_content("composer.json", identifier)?; + let composer = VcsDriverBase::finish_base_composer_information( + identifier, + file_content, + || self.get_change_date(identifier), + )?; if self.inner.should_cache(identifier) { if let Some(ref composer_map) = composer { @@ -679,11 +707,11 @@ impl GitLabDriver { Err(e) => { self.git_driver = None; - self.inner.io.write_error( - PhpMixed::String(format!( + self.inner.io.write_error3( + &format!( "<error>Failed to clone the {} repository, try running in interactive mode so that you can enter your credentials</error>", url - )), + ), true, io_interface::NORMAL, ); @@ -721,8 +749,8 @@ impl GitLabDriver { repo_config, self.inner.io.clone(), self.inner.config.clone(), - self.inner.http_downloader.clone(), - self.inner.process.clone(), + std::rc::Rc::clone(&self.inner.http_downloader), + std::rc::Rc::clone(&self.inner.process), ); git_driver.initialize()?; self.git_driver = Some(git_driver); @@ -780,11 +808,8 @@ impl GitLabDriver { } if !more_than_guest_access { - self.inner.io.write_error( - PhpMixed::String( - "<warning>GitLab token with Guest or Planner only access detected</warning>" - .to_string(), - ), + self.inner.io.write_error3( + "<warning>GitLab token with Guest or Planner only access detected</warning>", true, io_interface::NORMAL, ); @@ -840,11 +865,11 @@ impl GitLabDriver { } Err(e) => { let mut git_lab_util = GitLab::new( - self.inner.io.as_ref(), - &self.inner.config, - &self.inner.process, - &self.inner.http_downloader, - ); + self.inner.io.clone_box(), + std::rc::Rc::clone(&self.inner.config), + Some(std::rc::Rc::clone(&self.inner.process)), + Some(std::rc::Rc::clone(&self.inner.http_downloader)), + )?; match e.code { 401 | 404 => { @@ -882,11 +907,11 @@ impl GitLabDriver { .unwrap() .unwrap()); } - self.inner.io.write_error( - PhpMixed::String(format!( + self.inner.io.write_error3( + &format!( "<warning>Failed to download {}/{}:{}</warning>", self.namespace, self.repository, e.message - )), + ), true, io_interface::NORMAL, ); @@ -938,25 +963,39 @@ impl GitLabDriver { /// Uses the config `gitlab-domains` to see if the driver supports the url for the /// repository given. pub fn supports(io: &dyn IOInterface, config: &Config, url: &str, _deep: bool) -> bool { - let match_ = match Preg::is_match_strict_groups(Self::URL_REGEX, url) { - Some(m) => m, - None => return false, - }; + let mut match_: IndexMap<CaptureKey, String> = IndexMap::new(); + if !Preg::is_match_strict_groups3(Self::URL_REGEX, url, Some(&mut match_)).unwrap_or(false) + { + return false; + } - let scheme = match_.get("scheme").cloned().unwrap_or_default(); + let scheme = match_ + .get(&CaptureKey::ByName("scheme".to_string())) + .cloned() + .unwrap_or_default(); let guessed_domain = match_ - .get("domain") + .get(&CaptureKey::ByName("domain".to_string())) .cloned() .filter(|s| !s.is_empty()) - .unwrap_or_else(|| match_.get("domain2").cloned().unwrap_or_default()); - let mut url_parts: Vec<String> = - explode("/", &match_.get("parts").cloned().unwrap_or_default()); + .unwrap_or_else(|| { + match_ + .get(&CaptureKey::ByName("domain2".to_string())) + .cloned() + .unwrap_or_default() + }); + let mut url_parts: Vec<String> = explode( + "/", + &match_ + .get(&CaptureKey::ByName("parts".to_string())) + .cloned() + .unwrap_or_default(), + ); if Self::determine_origin( &config.get("gitlab-domains"), guessed_domain, &mut url_parts, - match_.get("port").cloned(), + match_.get(&CaptureKey::ByName("port".to_string())).cloned(), ) .is_none() { @@ -964,11 +1003,11 @@ impl GitLabDriver { } if scheme == "https" && !extension_loaded("openssl") { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "Skipping GitLab driver for {} because the OpenSSL PHP extension is missing.", url - )), + ), true, io_interface::VERBOSE, ); @@ -993,8 +1032,16 @@ impl GitLabDriver { let links = explode(",", &header); for link in &links { - if let Some(match_) = Preg::is_match_strict_groups(r#"{<(.+?)>; *rel="next"}"#, link) { - return Some(match_.get(1).cloned().unwrap_or_default()); + let mut match_: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3(r#"{<(.+?)>; *rel="next"}"#, link, Some(&mut match_)) + .unwrap_or(false) + { + return Some( + match_ + .get(&CaptureKey::ByIndex(1)) + .cloned() + .unwrap_or_default(), + ); } } @@ -1048,7 +1095,7 @@ impl GitLabDriver { false, ) || (port_number.is_some() && in_array( - PhpMixed::String(Preg::replace(r"{:\d+}", "", guessed_domain.clone())), + PhpMixed::String(Preg::replace(r"{:\d+}", "", &guessed_domain)), configured_domains, false, )) diff --git a/crates/shirabe/src/repository/vcs/hg_driver.rs b/crates/shirabe/src/repository/vcs/hg_driver.rs index 68393ed..f7c0c16 100644 --- a/crates/shirabe/src/repository/vcs/hg_driver.rs +++ b/crates/shirabe/src/repository/vcs/hg_driver.rs @@ -30,6 +30,7 @@ impl HgDriver { let cache_vcs_dir = self .inner .config + .borrow_mut() .get("cache-vcs-dir") .as_string() .unwrap_or("") @@ -58,30 +59,32 @@ impl HgDriver { }.into()); } - self.inner - .config - .prohibit_url_by_config(&self.inner.url, &*self.inner.io)?; + self.inner.config.borrow_mut().prohibit_url_by_config( + &self.inner.url, + Some(&*self.inner.io), + &indexmap::IndexMap::new(), + )?; - let hg_utils = HgUtils::new(&*self.inner.io, &self.inner.config, &self.inner.process); + let hg_utils = HgUtils::new( + &*self.inner.io, + &*self.inner.config.borrow(), + &self.inner.process, + ); if is_dir(&self.repo_dir) - && self.inner.process.execute( + && self.inner.process.borrow_mut().execute_args( &["hg", "summary"].map(|s| s.to_string()).to_vec(), &mut String::new(), Some(self.repo_dir.clone()), ) == 0 { - if self.inner.process.execute( + if self.inner.process.borrow_mut().execute_args( &["hg", "pull"].map(|s| s.to_string()).to_vec(), &mut String::new(), Some(self.repo_dir.clone()), ) != 0 { - self.inner.io.write_error( - format!("<error>Failed to update {}, package information from this repository may be outdated ({})</error>", self.inner.url, self.inner.process.get_error_output()).into(), - true, - crate::io::io_interface::NORMAL, - ); + self.inner.io.write_error3(format!("<error>Failed to update {}, package information from this repository may be outdated ({})</error>", self.inner.url, self.inner.process.borrow().get_error_output()).into(), true, crate::io::io_interface::NORMAL); } } else { let fs2 = Filesystem::new(None); @@ -112,14 +115,14 @@ impl HgDriver { pub fn get_root_identifier(&mut self) -> anyhow::Result<String> { if self.root_identifier.is_none() { let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["hg", "tip", "--template", "{node}"] .map(|s| s.to_string()) .to_vec(), &mut output, Some(self.repo_dir.clone()), ); - let lines = self.inner.process.split_lines(&output); + let lines = self.inner.process.borrow().split_lines(&output); self.root_identifier = lines.into_iter().next(); } @@ -163,9 +166,11 @@ impl HgDriver { file.to_string(), ]; let mut content = String::new(); - self.inner - .process - .execute(&resource, &mut content, Some(self.repo_dir.clone())); + self.inner.process.borrow_mut().execute_args( + &resource, + &mut content, + Some(self.repo_dir.clone()), + ); if content.trim().is_empty() { return Ok(None); @@ -187,7 +192,7 @@ impl HgDriver { } let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &[ "hg", "log", @@ -210,12 +215,12 @@ impl HgDriver { if self.tags.is_none() { let mut tags: IndexMap<String, String> = IndexMap::new(); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["hg", "tags"].map(|s| s.to_string()).to_vec(), &mut output, Some(self.repo_dir.clone()), ); - for tag in self.inner.process.split_lines(&output) { + for tag in self.inner.process.borrow().split_lines(&output) { if !tag.is_empty() { if let Some(m) = Preg::match_(r"^([^\s]+)\s+\d+:(.*)$", &tag) { tags.insert( @@ -239,12 +244,12 @@ impl HgDriver { let mut bookmarks: IndexMap<String, String> = IndexMap::new(); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["hg", "branches"].map(|s| s.to_string()).to_vec(), &mut output, Some(self.repo_dir.clone()), ); - for branch in self.inner.process.split_lines(&output) { + for branch in self.inner.process.borrow().split_lines(&output) { if !branch.is_empty() { if let Some(m) = Preg::match_(r"^([^\s]+)\s+\d+:([a-f0-9]+)", &branch) { let name = m.get("1").cloned().unwrap_or_default(); @@ -256,12 +261,12 @@ impl HgDriver { } output.clear(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["hg", "bookmarks"].map(|s| s.to_string()).to_vec(), &mut output, Some(self.repo_dir.clone()), ); - for branch in self.inner.process.split_lines(&output) { + for branch in self.inner.process.borrow().split_lines(&output) { if !branch.is_empty() { if let Some(m) = Preg::match_(r"^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$", &branch) { let name = m.get("1").cloned().unwrap_or_default(); @@ -298,7 +303,7 @@ impl HgDriver { let process = crate::util::process_executor::ProcessExecutor::new(io); let mut output = String::new(); - if process.execute( + if process.execute_args( &["hg", "summary"].map(|s| s.to_string()).to_vec(), &mut output, Some(url), @@ -314,7 +319,7 @@ impl HgDriver { let process = crate::util::process_executor::ProcessExecutor::new(io); let mut ignored = String::new(); - let exit = process.execute( + let exit = process.execute_args( &["hg", "identify", "--", url] .map(|s| s.to_string()) .to_vec(), diff --git a/crates/shirabe/src/repository/vcs/perforce_driver.rs b/crates/shirabe/src/repository/vcs/perforce_driver.rs index ee09dee..e3aa868 100644 --- a/crates/shirabe/src/repository/vcs/perforce_driver.rs +++ b/crates/shirabe/src/repository/vcs/perforce_driver.rs @@ -59,6 +59,7 @@ impl PerforceDriver { let cache_vcs_dir = self .inner .config + .borrow_mut() .get("cache-vcs-dir") .as_string() .unwrap_or("") diff --git a/crates/shirabe/src/repository/vcs/svn_driver.rs b/crates/shirabe/src/repository/vcs/svn_driver.rs index 2e649df..8218563 100644 --- a/crates/shirabe/src/repository/vcs/svn_driver.rs +++ b/crates/shirabe/src/repository/vcs/svn_driver.rs @@ -3,7 +3,7 @@ use anyhow::Result; use chrono::{DateTime, TimeZone, Utc}; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE, PhpMixed, RuntimeException, array_key_exists, is_array, max, sprintf, stripos, strrpos, strtr, substr, trim, @@ -90,6 +90,7 @@ impl SvnDriver { "{}/{}", self.inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or(""), @@ -102,6 +103,7 @@ impl SvnDriver { self.inner.cache.as_mut().unwrap().set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -135,7 +137,10 @@ impl SvnDriver { } pub(crate) fn should_cache(&self, identifier: &str) -> bool { - self.inner.cache.is_some() && Preg::is_match(r"{@\d+$}", identifier) + self.inner.cache.is_some() + && Preg::is_match(r"{@\d+$}", identifier) + .unwrap_or(false) + .unwrap_or(false) } pub fn get_composer_information( @@ -170,22 +175,30 @@ impl SvnDriver { } // TODO(phase-b): use anyhow::Result<Result<T, E>> to model PHP try/catch - let composer: Option<IndexMap<String, PhpMixed>> = - match self.inner.get_base_composer_information(identifier) { - Ok(c) => c, - Err(e) => { - // TODO(phase-b): downcast to TransportException - let _te: &TransportException = todo!("downcast e to TransportException"); - let message = e.to_string(); - if stripos(&message, "path not found").is_none() - && stripos(&message, "svn: warning: W160013").is_none() - { - return Err(e); - } - // remember a not-existent composer.json - None + let base_result = + self.get_file_content("composer.json", identifier) + .and_then(|file_content| { + VcsDriverBase::finish_base_composer_information( + identifier, + file_content, + || self.get_change_date(identifier), + ) + }); + let composer: Option<IndexMap<String, PhpMixed>> = match base_result { + Ok(c) => c, + Err(e) => { + // TODO(phase-b): downcast to TransportException + let _te: &TransportException = todo!("downcast e to TransportException"); + let message = e.to_string(); + if stripos(&message, "path not found").is_none() + && stripos(&message, "svn: warning: W160013").is_none() + { + return Err(e); } - }; + // remember a not-existent composer.json + None + } + }; if self.should_cache(identifier) { let encoded = JsonFile::encode( @@ -282,12 +295,17 @@ impl SvnDriver { vec!["svn".to_string(), "info".to_string()], &format!("{}{}{}", self.base_url, path, rev), )?; - for line in self.inner.process.split_lines(&output) { + for line in self.inner.process.borrow().split_lines(&output) { if !line.is_empty() { - if let Some(m) = - Preg::is_match_strict_groups(r"{^Last Changed Date: ([^(]+)}", &line) + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( + r"{^Last Changed Date: ([^(]+)}", + &line, + Some(&mut m), + ) + .unwrap_or(false) { - let date_str = m.get(1).cloned().unwrap_or_default(); + let date_str = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); // PHP: new \DateTimeImmutable($match[1], new \DateTimeZone('UTC')) return Ok(Utc .datetime_from_str(date_str.trim(), "%Y-%m-%d %H:%M:%S %z") @@ -313,15 +331,23 @@ impl SvnDriver { .unwrap_or_default(); if !output.is_empty() { let mut last_rev: i64 = 0; - for line in self.inner.process.split_lines(&output) { + for line in self.inner.process.borrow().split_lines(&output) { let line = trim(&line, None); if !line.is_empty() { - if let Some(m) = - Preg::is_match_strict_groups(r"{^\s*(\S+).*?(\S+)\s*$}", &line) + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( + r"{^\s*(\S+).*?(\S+)\s*$}", + &line, + Some(&mut m), + ) + .unwrap_or(false) { - let rev: i64 = - m.get(1).map(|s| s.parse().unwrap_or(0)).unwrap_or(0); - let path = m.get(2).cloned().unwrap_or_default(); + let rev: i64 = m + .get(&CaptureKey::ByIndex(1)) + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + let path = + m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); if path == "./" { last_rev = rev; } else { @@ -360,14 +386,22 @@ impl SvnDriver { ) .unwrap_or_default(); if !output.is_empty() { - for line in self.inner.process.split_lines(&output) { + for line in self.inner.process.borrow().split_lines(&output) { let line = trim(&line, None); if !line.is_empty() { - if let Some(m) = - Preg::is_match_strict_groups(r"{^\s*(\S+).*?(\S+)\s*$}", &line) + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( + r"{^\s*(\S+).*?(\S+)\s*$}", + &line, + Some(&mut m), + ) + .unwrap_or(false) { - let rev: i64 = m.get(1).map(|s| s.parse().unwrap_or(0)).unwrap_or(0); - let path = m.get(2).cloned().unwrap_or_default(); + let rev: i64 = m + .get(&CaptureKey::ByIndex(1)) + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + let path = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); if path == "./" { let identifier = self.build_identifier( &format!("/{}", self.trunk_path.clone().unwrap_or_default()), @@ -393,15 +427,28 @@ impl SvnDriver { .unwrap_or_default(); if !output.is_empty() { let mut last_rev: i64 = 0; - for line in self.inner.process.split_lines(&trim(&output, None)) { + for line in self + .inner + .process + .borrow() + .split_lines(&trim(&output, None)) + { let line = trim(&line, None); if !line.is_empty() { - if let Some(m) = - Preg::is_match_strict_groups(r"{^\s*(\S+).*?(\S+)\s*$}", &line) + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( + r"{^\s*(\S+).*?(\S+)\s*$}", + &line, + Some(&mut m), + ) + .unwrap_or(false) { - let rev: i64 = - m.get(1).map(|s| s.parse().unwrap_or(0)).unwrap_or(0); - let path = m.get(2).cloned().unwrap_or_default(); + let rev: i64 = m + .get(&CaptureKey::ByIndex(1)) + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + let path = + m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); if path == "./" { last_rev = rev; } else { @@ -426,7 +473,10 @@ impl SvnDriver { pub fn supports(io: &dyn IOInterface, _config: &Config, url: &str, deep: bool) -> bool { let url = Self::normalize_url(url); - if Preg::is_match(r"#(^svn://|^svn\+ssh://|svn\.)#i", &url) { + if Preg::is_match(r"#(^svn://|^svn\+ssh://|svn\.)#i", &url) + .unwrap_or(false) + .unwrap_or(false) + { return true; } @@ -437,7 +487,7 @@ impl SvnDriver { let mut process = ProcessExecutor::new(io); let mut ignored_output = String::new(); - let exit = process.execute( + let exit = process.execute_args( &[ "svn".to_string(), "info".to_string(), @@ -516,7 +566,7 @@ impl SvnDriver { message: format!( "Failed to load {}, svn was not found, check that it is installed and in your PATH env.\n\n{}", self.inner.url, - self.inner.process.get_error_output(), + self.inner.process.borrow().get_error_output(), ), code: 0, } diff --git a/crates/shirabe/src/repository/vcs/vcs_driver.rs b/crates/shirabe/src/repository/vcs/vcs_driver.rs index 162792e..e356a6f 100644 --- a/crates/shirabe/src/repository/vcs/vcs_driver.rs +++ b/crates/shirabe/src/repository/vcs/vcs_driver.rs @@ -1,5 +1,6 @@ //! ref: composer/src/Composer/Repository/Vcs/VcsDriver.php +use chrono::{DateTime, Utc}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_php_shim::{ @@ -23,9 +24,9 @@ pub struct VcsDriverBase { pub origin_url: String, pub repo_config: IndexMap<String, PhpMixed>, pub io: Box<dyn IOInterface>, - pub config: Config, - pub process: ProcessExecutor, - pub http_downloader: HttpDownloader, + pub config: std::rc::Rc<std::cell::RefCell<Config>>, + pub process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, + pub http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, pub info_cache: IndexMap<String, Option<IndexMap<String, PhpMixed>>>, pub cache: Option<Cache>, } @@ -34,9 +35,9 @@ impl VcsDriverBase { pub fn new( repo_config: IndexMap<String, PhpMixed>, io: Box<dyn IOInterface>, - config: Config, - http_downloader: HttpDownloader, - process: ProcessExecutor, + config: std::rc::Rc<std::cell::RefCell<Config>>, + http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, + process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, ) -> Self { let url = repo_config .get("url") @@ -74,7 +75,52 @@ impl VcsDriverBase { .get("options") .cloned() .unwrap_or(PhpMixed::Array(IndexMap::new())); - self.http_downloader.get(url, &options) + self.http_downloader.borrow_mut().get(url, &options) + } + + // Helper for concrete drivers: produces the same value as the trait default + // `get_base_composer_information`, but receives a pre-fetched composer.json + // body and a lazy change-date callback. Concrete drivers in the Rust port + // wrap `VcsDriverBase` as `self.inner` instead of inheriting from it, so + // they cannot dispatch back into a base method that calls `get_file_content` + // / `get_change_date` hooks; the caller threads those calls in itself. + pub fn finish_base_composer_information( + identifier: &str, + composer_file_content: Option<String>, + change_date: impl FnOnce() -> anyhow::Result<Option<DateTime<Utc>>>, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + let content = match composer_file_content { + None => return Ok(None), + Some(c) if c.is_empty() => return Ok(None), + Some(c) => c, + }; + + let parsed = JsonFile::parse_json( + Some(&content), + Some(&format!("{}:composer.json", identifier)), + )?; + + let array = match parsed { + PhpMixed::Array(a) if !a.is_empty() => a, + _ => return Ok(None), + }; + + // PHP arrays own their nested values; the Rust representation wraps them + // in Box<PhpMixed>. Unbox the outer level so callers can mutate keys. + let mut composer: IndexMap<String, PhpMixed> = + array.into_iter().map(|(k, v)| (k, *v)).collect(); + + if !composer.contains_key("time") + || composer + .get("time") + .map_or(true, |v| v.as_string().map_or(true, |s| s.is_empty())) + { + if let Some(d) = change_date()? { + composer.insert("time".to_string(), PhpMixed::String(d.to_rfc3339())); + } + } + + Ok(Some(composer)) } } @@ -93,8 +139,7 @@ pub trait VcsDriver: VcsDriverInterface { fn config_mut(&mut self) -> &mut Config; fn process(&self) -> &ProcessExecutor; fn process_mut(&mut self) -> &mut ProcessExecutor; - fn http_downloader(&self) -> &HttpDownloader; - fn http_downloader_mut(&mut self) -> &mut HttpDownloader; + fn http_downloader(&self) -> &std::rc::Rc<std::cell::RefCell<HttpDownloader>>; fn info_cache(&self) -> &IndexMap<String, Option<IndexMap<String, PhpMixed>>>; fn info_cache_mut(&mut self) -> &mut IndexMap<String, Option<IndexMap<String, PhpMixed>>>; fn cache(&self) -> Option<&Cache>; @@ -195,7 +240,7 @@ pub trait VcsDriver: VcsDriverInterface { .get("options") .cloned() .unwrap_or(PhpMixed::Array(IndexMap::new())); - self.http_downloader().get(url, &options) + self.http_downloader().borrow_mut().get(url, &options) } fn cleanup(&self) {} diff --git a/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs b/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs index 5b15c2c..236b9b4 100644 --- a/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs +++ b/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs @@ -6,7 +6,7 @@ use chrono::{DateTime, Utc}; use indexmap::IndexMap; use shirabe_php_shim::PhpMixed; -pub trait VcsDriverInterface { +pub trait VcsDriverInterface: std::fmt::Debug { fn initialize(&mut self) -> anyhow::Result<()>; fn get_composer_information( |
