diff options
20 files changed, 1338 insertions, 1169 deletions
diff --git a/crates/shirabe/src/command/status_command.rs b/crates/shirabe/src/command/status_command.rs index 01c1b5e..457c546 100644 --- a/crates/shirabe/src/command/status_command.rs +++ b/crates/shirabe/src/command/status_command.rs @@ -128,13 +128,11 @@ impl StatusCommand { Some(d) => d, None => continue, }; - // TODO(phase-b): downloader borrow lifetime tied to dm.borrow() temporary; restructure later. - let dm_borrow = dm.borrow(); - let downloader: &dyn crate::downloader::DownloaderInterface = - match dm_borrow.get_downloader_for_package(package.clone())? { - Some(d) => d, - None => continue, - }; + let downloader_handle = match dm.borrow().get_downloader_for_package(package.clone())? { + Some(d) => d, + None => continue, + }; + let mut downloader = downloader_handle.borrow_mut(); // TODO(phase-b): isinstance checks using ChangeReportInterface/VcsCapableDownloaderInterface/DvcsDownloaderInterface if let Some(change_reporter) = downloader.as_change_report_interface() { diff --git a/crates/shirabe/src/downloader/change_report_interface.rs b/crates/shirabe/src/downloader/change_report_interface.rs index 5e770f7..718b2c1 100644 --- a/crates/shirabe/src/downloader/change_report_interface.rs +++ b/crates/shirabe/src/downloader/change_report_interface.rs @@ -6,7 +6,7 @@ use crate::package::PackageInterfaceHandle; pub trait ChangeReportInterface { fn get_local_changes( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, ) -> Result<Option<String>>; diff --git a/crates/shirabe/src/downloader/download_manager.rs b/crates/shirabe/src/downloader/download_manager.rs index 7d5d76d..e0998e9 100644 --- a/crates/shirabe/src/downloader/download_manager.rs +++ b/crates/shirabe/src/downloader/download_manager.rs @@ -31,7 +31,7 @@ pub struct DownloadManager { /// @var Filesystem filesystem: std::rc::Rc<std::cell::RefCell<Filesystem>>, /// @var array<string, DownloaderInterface> - downloaders: IndexMap<String, Box<dyn DownloaderInterface>>, + downloaders: IndexMap<String, std::rc::Rc<std::cell::RefCell<dyn DownloaderInterface>>>, } impl DownloadManager { @@ -91,7 +91,7 @@ impl DownloadManager { pub fn set_downloader( &mut self, r#type: &str, - downloader: Box<dyn DownloaderInterface>, + downloader: std::rc::Rc<std::cell::RefCell<dyn DownloaderInterface>>, ) -> &mut Self { let r#type = strtolower(r#type); self.downloaders.insert(r#type, downloader); @@ -103,7 +103,10 @@ impl DownloadManager { /// /// @param string $type installation type /// @throws \InvalidArgumentException if downloader for provided type is not registered - pub fn get_downloader(&self, r#type: &str) -> Result<&dyn DownloaderInterface> { + pub fn get_downloader( + &self, + r#type: &str, + ) -> Result<std::rc::Rc<std::cell::RefCell<dyn DownloaderInterface>>> { let r#type = strtolower(r#type); if !self.downloaders.contains_key(&r#type) { return Err(InvalidArgumentException { @@ -119,7 +122,7 @@ impl DownloadManager { .into()); } - Ok(self.downloaders.get(&r#type).unwrap().as_ref()) + Ok(self.downloaders.get(&r#type).unwrap().clone()) } /// Returns downloader for already installed package. @@ -131,7 +134,7 @@ impl DownloadManager { pub fn get_downloader_for_package( &self, package: PackageInterfaceHandle, - ) -> Result<Option<&dyn DownloaderInterface>> { + ) -> Result<Option<std::rc::Rc<std::cell::RefCell<dyn DownloaderInterface>>>> { let installation_source = package.get_installation_source(); if "metapackage" == package.get_type() { @@ -153,13 +156,14 @@ impl DownloadManager { .into()); }; - if installation_source.as_deref() != Some(&downloader.get_installation_source()) { + let downloader_installation_source = downloader.borrow().get_installation_source(); + if installation_source.as_deref() != Some(&downloader_installation_source) { return Err(LogicException { message: sprintf( "Downloader \"%s\" is a %s type downloader and can not be used to download %s for package %s", &[ - PhpMixed::String(shirabe_php_shim::get_class_obj(downloader)), - PhpMixed::String(downloader.get_installation_source()), + PhpMixed::String(shirabe_php_shim::get_class_obj(&*downloader.borrow())), + PhpMixed::String(downloader_installation_source), PhpMixed::String(installation_source.clone().unwrap_or_default()), PhpMixed::String(package.to_string()), ], @@ -172,13 +176,13 @@ impl DownloadManager { Ok(Some(downloader)) } - pub fn get_downloader_type(&self, downloader: &dyn DownloaderInterface) -> String { + pub fn get_downloader_type( + &self, + downloader: &std::rc::Rc<std::cell::RefCell<dyn DownloaderInterface>>, + ) -> String { // PHP: array_search($downloader, $this->downloaders) for (r#type, candidate) in &self.downloaders { - if std::ptr::eq( - candidate.as_ref() as *const dyn DownloaderInterface as *const (), - downloader as *const dyn DownloaderInterface as *const (), - ) { + if std::rc::Rc::ptr_eq(candidate, downloader) { return r#type.clone(); } } @@ -238,6 +242,7 @@ impl DownloadManager { }; let result = match downloader + .borrow_mut() .download3(package.clone(), &target_dir, prev_package.clone()) .await { @@ -299,6 +304,7 @@ impl DownloadManager { let target_dir = self.normalize_target_dir(target_dir); if let Some(downloader) = self.get_downloader_for_package(package.clone())? { return downloader + .borrow_mut() .prepare(r#type, package, &target_dir, prev_package) .await; } @@ -321,7 +327,7 @@ impl DownloadManager { ) -> Result<Option<PhpMixed>> { let target_dir = self.normalize_target_dir(target_dir); if let Some(downloader) = self.get_downloader_for_package(package.clone())? { - return downloader.install2(package, &target_dir).await; + return downloader.borrow_mut().install2(package, &target_dir).await; } Ok(None) @@ -353,16 +359,20 @@ impl DownloadManager { // if we have a downloader present before, but not after, the package became a metapackage and its files should be removed if downloader.is_none() { return initial_downloader + .as_ref() .unwrap() + .borrow_mut() .remove2(initial, &target_dir) .await; } - let initial_type = self.get_downloader_type(initial_downloader.unwrap()); - let target_type = self.get_downloader_type(downloader.unwrap()); + let initial_type = self.get_downloader_type(initial_downloader.as_ref().unwrap()); + let target_type = self.get_downloader_type(downloader.as_ref().unwrap()); if initial_type == target_type { match downloader + .as_ref() .unwrap() + .borrow_mut() .update(initial.clone(), target.clone(), &target_dir) .await { @@ -399,7 +409,9 @@ impl DownloadManager { // we wipe the dir and do a new install instead of updating it // PHP: return $promise->then(fn () => $this->install($target, $targetDir)); let _ = initial_downloader + .as_ref() .unwrap() + .borrow_mut() .remove2(initial, &target_dir) .await?; self.install(target, &target_dir).await @@ -417,7 +429,7 @@ impl DownloadManager { ) -> Result<Option<PhpMixed>> { let target_dir = self.normalize_target_dir(target_dir); if let Some(downloader) = self.get_downloader_for_package(package.clone())? { - return downloader.remove2(package, &target_dir).await; + return downloader.borrow_mut().remove2(package, &target_dir).await; } Ok(None) @@ -440,6 +452,7 @@ impl DownloadManager { let target_dir = self.normalize_target_dir(target_dir); if let Some(downloader) = self.get_downloader_for_package(package.clone())? { return downloader + .borrow_mut() .cleanup(r#type, package, &target_dir, prev_package) .await; } diff --git a/crates/shirabe/src/downloader/downloader_interface.rs b/crates/shirabe/src/downloader/downloader_interface.rs index 873438e..f63ac4e 100644 --- a/crates/shirabe/src/downloader/downloader_interface.rs +++ b/crates/shirabe/src/downloader/downloader_interface.rs @@ -8,7 +8,7 @@ pub trait DownloaderInterface: std::fmt::Debug { fn get_installation_source(&self) -> String; async fn download( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, prev_package: Option<PackageInterfaceHandle>, @@ -17,7 +17,7 @@ pub trait DownloaderInterface: std::fmt::Debug { /// Convenience for the PHP default `$output = true` overload. async fn download3( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, prev_package: Option<PackageInterfaceHandle>, @@ -26,7 +26,7 @@ pub trait DownloaderInterface: std::fmt::Debug { } async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, @@ -34,7 +34,7 @@ pub trait DownloaderInterface: std::fmt::Debug { ) -> anyhow::Result<Option<PhpMixed>>; async fn install( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -42,7 +42,7 @@ pub trait DownloaderInterface: std::fmt::Debug { /// Convenience for the PHP default `$output = true` overload. async fn install2( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, ) -> anyhow::Result<Option<PhpMixed>> { @@ -50,14 +50,14 @@ pub trait DownloaderInterface: std::fmt::Debug { } async fn update( - &self, + &mut self, initial: PackageInterfaceHandle, target: PackageInterfaceHandle, path: &str, ) -> anyhow::Result<Option<PhpMixed>>; async fn remove( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -65,7 +65,7 @@ pub trait DownloaderInterface: std::fmt::Debug { /// Convenience for the PHP default `$output = true` overload. async fn remove2( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, ) -> anyhow::Result<Option<PhpMixed>> { @@ -73,14 +73,16 @@ pub trait DownloaderInterface: std::fmt::Debug { } async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>>; - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { None } diff --git a/crates/shirabe/src/downloader/file_downloader.rs b/crates/shirabe/src/downloader/file_downloader.rs index 55882f7..e0fc0c9 100644 --- a/crates/shirabe/src/downloader/file_downloader.rs +++ b/crates/shirabe/src/downloader/file_downloader.rs @@ -153,13 +153,15 @@ impl DownloaderInterface for FileDownloader { "dist".to_owned() } - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } /// @inheritDoc async fn download( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, _prev_package: Option<PackageInterfaceHandle>, @@ -437,7 +439,7 @@ impl DownloaderInterface for FileDownloader { /// @inheritDoc async fn prepare( - &self, + &mut self, _type: &str, _package: PackageInterfaceHandle, _path: &str, @@ -448,7 +450,7 @@ impl DownloaderInterface for FileDownloader { /// @inheritDoc async fn cleanup( - &self, + &mut self, _type: &str, package: PackageInterfaceHandle, path: &str, @@ -503,7 +505,7 @@ impl DownloaderInterface for FileDownloader { /// @inheritDoc async fn install( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -565,7 +567,7 @@ impl DownloaderInterface for FileDownloader { /// @inheritDoc async fn update( - &self, + &mut self, initial: PackageInterfaceHandle, target: PackageInterfaceHandle, path: &str, @@ -583,7 +585,7 @@ impl DownloaderInterface for FileDownloader { /// @inheritDoc async fn remove( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -615,7 +617,7 @@ impl ChangeReportInterface for FileDownloader { /// @inheritDoc /// @throws \RuntimeException fn get_local_changes( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, ) -> Result<Option<String>> { diff --git a/crates/shirabe/src/downloader/fossil_downloader.rs b/crates/shirabe/src/downloader/fossil_downloader.rs index 9a38578..d6b8095 100644 --- a/crates/shirabe/src/downloader/fossil_downloader.rs +++ b/crates/shirabe/src/downloader/fossil_downloader.rs @@ -4,6 +4,7 @@ use crate::config::Config; use crate::downloader::ChangeReportInterface; use crate::downloader::DownloaderInterface; use crate::downloader::VcsCapableDownloaderInterface; +use crate::downloader::VcsDownloader; use crate::downloader::VcsDownloaderBase; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; @@ -11,6 +12,7 @@ use crate::package::PackageInterfaceHandle; use crate::util::Filesystem; use crate::util::ProcessExecutor; use anyhow::Result; +use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; use shirabe_php_shim::{PhpMixed, RuntimeException}; @@ -31,30 +33,82 @@ impl FossilDownloader { } } - pub(crate) async fn do_download( + fn execute( &self, + command: Vec<String>, + cwd: Option<String>, + output: &mut String, + ) -> Result<()> { + if self + .inner + .process + .borrow_mut() + .execute(&command, output, cwd)? + != 0 + { + return Err(RuntimeException { + message: format!( + "Failed to execute {}\n\n{}", + command.join(" "), + self.inner.process.borrow().get_error_output() + ), + code: 0, + } + .into()); + } + Ok(()) + } +} + +impl VcsDownloader for FossilDownloader { + fn io(&self) -> std::rc::Rc<std::cell::RefCell<dyn IOInterface>> { + self.inner.io.clone() + } + + fn config(&self) -> &std::rc::Rc<std::cell::RefCell<Config>> { + &self.inner.config + } + + fn process(&self) -> &std::rc::Rc<std::cell::RefCell<ProcessExecutor>> { + &self.inner.process + } + + fn filesystem(&self) -> &std::rc::Rc<std::cell::RefCell<Filesystem>> { + &self.inner.filesystem + } + + fn has_cleaned_changes(&self) -> &IndexMap<String, bool> { + &self.inner.has_cleaned_changes + } + + fn has_cleaned_changes_mut(&mut self) -> &mut IndexMap<String, bool> { + &mut self.inner.has_cleaned_changes + } + + async fn do_download( + &mut self, _package: PackageInterfaceHandle, - _path: String, - _url: String, + _path: &str, + _url: &str, _prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { Ok(None) } - pub(crate) async fn do_install( - &self, + async fn do_install( + &mut self, package: PackageInterfaceHandle, - path: String, - url: String, + path: &str, + url: &str, ) -> Result<Option<PhpMixed>> { self.inner.config.borrow_mut().prohibit_url_by_config( - &url, + url, Some(self.inner.io.clone()), &indexmap::IndexMap::new(), )?; let repo_file = format!("{}.fossil", path); - let real_path = shirabe_php_shim::realpath(&path); + let real_path = shirabe_php_shim::realpath(path); self.inner.io.write_error(&format!( "Cloning {}", @@ -67,7 +121,7 @@ impl FossilDownloader { "fossil".to_string(), "clone".to_string(), "--".to_string(), - url, + url.to_string(), repo_file.clone(), ], None, @@ -101,15 +155,15 @@ impl FossilDownloader { Ok(None) } - pub(crate) async fn do_update( - &self, + async fn do_update( + &mut self, _initial: PackageInterfaceHandle, target: PackageInterfaceHandle, - path: String, - url: String, + path: &str, + url: &str, ) -> Result<Option<PhpMixed>> { self.inner.config.borrow_mut().prohibit_url_by_config( - &url, + url, Some(self.inner.io.clone()), &indexmap::IndexMap::new(), )?; @@ -119,7 +173,7 @@ impl FossilDownloader { target.get_source_reference().unwrap_or_default() )); - if !self.has_metadata_repository(&path) { + if !self.has_metadata_repository(path) { return Err(RuntimeException { message: format!( "The .fslckout file is missing from {}, see https://getcomposer.org/commit-deps for more information", @@ -129,7 +183,7 @@ impl FossilDownloader { }.into()); } - let real_path = shirabe_php_shim::realpath(&path); + let real_path = shirabe_php_shim::realpath(path); let mut output = String::new(); self.execute( vec!["fossil".to_string(), "pull".to_string()], @@ -153,11 +207,11 @@ impl FossilDownloader { Ok(None) } - pub(crate) fn get_commit_logs( - &self, - _from_reference: String, - to_reference: String, - path: String, + fn get_commit_logs( + &mut self, + _from_reference: &str, + to_reference: &str, + path: &str, ) -> Result<String> { let mut output = String::new(); self.execute( @@ -171,9 +225,9 @@ impl FossilDownloader { "-n".to_string(), "0".to_string(), "before".to_string(), - to_reference.clone(), + to_reference.to_string(), ], - shirabe_php_shim::realpath(&path), + shirabe_php_shim::realpath(path), &mut output, )?; @@ -197,33 +251,7 @@ impl FossilDownloader { Ok(log) } - fn execute( - &self, - command: Vec<String>, - cwd: Option<String>, - output: &mut String, - ) -> Result<()> { - if self - .inner - .process - .borrow_mut() - .execute(&command, output, cwd)? - != 0 - { - return Err(RuntimeException { - message: format!( - "Failed to execute {}\n\n{}", - command.join(" "), - self.inner.process.borrow().get_error_output() - ), - code: 0, - } - .into()); - } - Ok(()) - } - - pub(crate) fn has_metadata_repository(&self, path: &str) -> bool { + fn has_metadata_repository(&self, path: &str) -> bool { std::path::Path::new(&format!("{}/.fslckout", path)).is_file() || std::path::Path::new(&format!("{}/_FOSSIL_", path)).is_file() } @@ -231,7 +259,7 @@ impl FossilDownloader { impl ChangeReportInterface for FossilDownloader { fn get_local_changes( - &self, + &mut self, _package: PackageInterfaceHandle, path: &str, ) -> Result<Option<String>> { @@ -258,12 +286,11 @@ impl VcsCapableDownloaderInterface for FossilDownloader { } } -// TODO(phase-b): wire up VcsDownloader trait properly. FossilDownloader extends VcsDownloader -// which implements DownloaderInterface in PHP. Delegating each trait method to todo!() until the -// inner VcsDownloaderBase exposes the matching impl surface. #[async_trait::async_trait(?Send)] impl DownloaderInterface for FossilDownloader { - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } @@ -274,63 +301,63 @@ impl DownloaderInterface for FossilDownloader { } fn get_installation_source(&self) -> String { - todo!() + <Self as VcsDownloader>::get_installation_source(self) } async fn download( - &self, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, _output: bool, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::download(self, package, path, prev_package).await } async fn prepare( - &self, - _type: &str, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + r#type: &str, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::prepare(self, r#type, package, path, prev_package).await } async fn install( - &self, - _package: PackageInterfaceHandle, - _path: &str, + &mut self, + package: PackageInterfaceHandle, + path: &str, _output: bool, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::install(self, package, path).await } async fn update( - &self, - _initial: PackageInterfaceHandle, - _target: PackageInterfaceHandle, - _path: &str, + &mut self, + initial: PackageInterfaceHandle, + target: PackageInterfaceHandle, + path: &str, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::update(self, initial, target, path).await } async fn remove( - &self, - _package: PackageInterfaceHandle, - _path: &str, + &mut self, + package: PackageInterfaceHandle, + path: &str, _output: bool, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::remove(self, package, path).await } async fn cleanup( - &self, - _type: &str, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + r#type: &str, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::cleanup(self, r#type, package, path, prev_package).await } } diff --git a/crates/shirabe/src/downloader/git_downloader.rs b/crates/shirabe/src/downloader/git_downloader.rs index 34f89a4..26ff4aa 100644 --- a/crates/shirabe/src/downloader/git_downloader.rs +++ b/crates/shirabe/src/downloader/git_downloader.rs @@ -14,6 +14,7 @@ use crate::config::Config; use crate::downloader::ChangeReportInterface; use crate::downloader::DvcsDownloaderInterface; use crate::downloader::VcsCapableDownloaderInterface; +use crate::downloader::VcsDownloader; use crate::downloader::VcsDownloaderBase; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; @@ -60,401 +61,6 @@ impl GitDownloader { } } - pub(crate) async fn do_download( - &mut self, - package: PackageInterfaceHandle, - _path: &str, - url: &str, - _prev_package: Option<PackageInterfaceHandle>, - ) -> Result<Option<PhpMixed>> { - // Do not create an extra local cache when repository is already local - if Filesystem::is_local_path(url) { - return Ok(None); - } - - GitUtil::clean_env(&self.inner.process); - - let cache_path = format!( - "{}/{}/", - self.inner - .config - .borrow_mut() - .get("cache-vcs-dir") - .as_string() - .unwrap_or(""), - Preg::replace(r"{[^a-z0-9.]}i", "-", &Url::sanitize(url.to_string()))?, - ); - let git_version = GitUtil::get_version(&self.inner.process); - - // --dissociate option is only available since git 2.3.0-rc0 - if git_version.is_some() - && version_compare(git_version.as_deref().unwrap_or(""), "2.3.0-rc0", ">=") - && Cache::is_usable(&cache_path) - { - self.inner.io.write_error3( - &format!( - " - Syncing <info>{}</info> (<comment>{}</comment>) into cache", - package.get_name(), - package - .get_full_pretty_version(true, crate::package::DisplayMode::SourceRefIfDev), - ), - true, - io_interface::NORMAL, - ); - self.inner.io.write_error3( - &sprintf( - " Cloning to cache at %s", - &[PhpMixed::String(cache_path.clone())], - ), - true, - io_interface::DEBUG, - ); - let r#ref = package.get_source_reference(); - let pretty_version = package.get_pretty_version(); - if self.git_util.fetch_ref_or_sync_mirror( - url, - &cache_path, - r#ref.as_deref().unwrap_or(""), - Some(&pretty_version), - )? && is_dir(&cache_path) - { - self.cached_packages - .entry(package.get_id()) - .or_insert_with(IndexMap::new) - .insert(r#ref.as_deref().unwrap_or("").to_string(), true); - } - } else if git_version.is_none() { - return Err(RuntimeException { - message: "git was not found in your PATH, skipping source download".to_string(), - code: 0, - } - .into()); - } - - Ok(None) - } - - pub(crate) async fn do_install( - &mut self, - package: PackageInterfaceHandle, - path: &str, - url: &str, - ) -> Result<Option<PhpMixed>> { - GitUtil::clean_env(&self.inner.process); - let path = self.normalize_path(path); - let cache_path = format!( - "{}/{}/", - self.inner - .config - .borrow_mut() - .get("cache-vcs-dir") - .as_string() - .unwrap_or(""), - Preg::replace(r"{[^a-z0-9.]}i", "-", &Url::sanitize(url.to_string()))?, - ); - let r#ref = package.get_source_reference().unwrap_or_default(); - - let msg; - let commands: Vec<Vec<String>>; - let has_cached = self - .cached_packages - .get(&package.get_id()) - .and_then(|m| m.get(&r#ref)) - .copied() - .unwrap_or(false); - if has_cached { - msg = format!("Cloning {} from cache", self.get_short_hash(&r#ref)); - - let mut clone_flags: Vec<String> = vec![ - "--dissociate".to_string(), - "--reference".to_string(), - cache_path.clone(), - ]; - let transport_options = package.get_transport_options(); - if let Some(git_opts) = transport_options.get("git").and_then(|v| v.as_array()) { - if let Some(single) = git_opts.get("single_use_clone").and_then(|v| v.as_bool()) { - if single { - clone_flags = vec![]; - } - } - } - - commands = vec![ - { - let mut base = vec![ - "git".to_string(), - "clone".to_string(), - "--no-checkout".to_string(), - cache_path.clone(), - path.clone(), - ]; - base.extend(clone_flags); - base - }, - vec![ - "git".to_string(), - "remote".to_string(), - "set-url".to_string(), - "origin".to_string(), - "--".to_string(), - "%sanitizedUrl%".to_string(), - ], - vec![ - "git".to_string(), - "remote".to_string(), - "add".to_string(), - "composer".to_string(), - "--".to_string(), - "%sanitizedUrl%".to_string(), - ], - ]; - } else { - msg = format!("Cloning {}", self.get_short_hash(&r#ref)); - commands = vec![ - vec![ - "git".to_string(), - "clone".to_string(), - "--no-checkout".to_string(), - "--".to_string(), - "%url%".to_string(), - path.clone(), - ], - vec![ - "git".to_string(), - "remote".to_string(), - "add".to_string(), - "composer".to_string(), - "--".to_string(), - "%url%".to_string(), - ], - vec![ - "git".to_string(), - "fetch".to_string(), - "composer".to_string(), - ], - vec![ - "git".to_string(), - "remote".to_string(), - "set-url".to_string(), - "origin".to_string(), - "--".to_string(), - "%sanitizedUrl%".to_string(), - ], - vec![ - "git".to_string(), - "remote".to_string(), - "set-url".to_string(), - "composer".to_string(), - "--".to_string(), - "%sanitizedUrl%".to_string(), - ], - ]; - if Platform::get_env("COMPOSER_DISABLE_NETWORK").is_some() { - return Err(RuntimeException { - message: format!( - "The required git reference for {} is not in cache and network is disabled, aborting", - package.get_name(), - ), - code: 0, - } - .into()); - } - } - - self.inner.io.write_error3(&msg, true, io_interface::NORMAL); - - self.git_util - .run_commands(commands, url, Some(&path), true, None)?; - - let source_url = package.get_source_url(); - if Some(url) != source_url.as_deref() && source_url.is_some() { - self.update_origin_url(&path, source_url.as_deref().unwrap()); - } else { - self.set_push_url(&path, url); - } - - let pretty_version = package.get_pretty_version(); - if let Some(new_ref) = - self.update_to_commit(package.clone(), &path, &r#ref, &pretty_version)? - { - if package.get_dist_reference() == package.get_source_reference() { - // TODO(phase-b): set_dist_reference requires &mut PackageInterface - // package.set_dist_reference(Some(new_ref.clone())); - } - // package.set_source_reference(Some(new_ref)); - let _ = new_ref; - } - - Ok(None) - } - - pub(crate) async fn do_update( - &mut self, - _initial: PackageInterfaceHandle, - target: PackageInterfaceHandle, - path: &str, - url: &str, - ) -> Result<Option<PhpMixed>> { - GitUtil::clean_env(&self.inner.process); - let path = self.normalize_path(path); - if !self.has_metadata_repository(&path) { - return Err(RuntimeException { - message: format!( - "The .git directory is missing from {}, see https://getcomposer.org/commit-deps for more information", - path - ), - code: 0, - } - .into()); - } - - let cache_path = format!( - "{}/{}/", - self.inner - .config - .borrow_mut() - .get("cache-vcs-dir") - .as_string() - .unwrap_or(""), - Preg::replace(r"{[^a-z0-9.]}i", "-", &Url::sanitize(url.to_string()))?, - ); - let r#ref = target.get_source_reference().unwrap_or_default(); - - let msg; - let remote_url; - let has_cached = self - .cached_packages - .get(&target.get_id()) - .and_then(|m| m.get(&r#ref)) - .copied() - .unwrap_or(false); - if has_cached { - msg = format!("Checking out {} from cache", self.get_short_hash(&r#ref)); - remote_url = cache_path.clone(); - } else { - msg = format!("Checking out {}", self.get_short_hash(&r#ref)); - remote_url = "%url%".to_string(); - if Platform::get_env("COMPOSER_DISABLE_NETWORK").is_some() { - return Err(RuntimeException { - message: format!( - "The required git reference for {} is not in cache and network is disabled, aborting", - target.get_name(), - ), - code: 0, - } - .into()); - } - } - - self.inner.io.write_error3(&msg, true, io_interface::NORMAL); - - let mut output = String::new(); - if self.inner.process.borrow_mut().execute_args( - &vec![ - "git".to_string(), - "rev-parse".to_string(), - "--quiet".to_string(), - "--verify".to_string(), - format!("{}^{{commit}}", r#ref), - ], - &mut output, - Some(path.clone()), - ) != 0 - { - let commands = vec![ - vec![ - "git".to_string(), - "remote".to_string(), - "set-url".to_string(), - "composer".to_string(), - "--".to_string(), - remote_url.clone(), - ], - vec![ - "git".to_string(), - "fetch".to_string(), - "composer".to_string(), - ], - vec![ - "git".to_string(), - "fetch".to_string(), - "--tags".to_string(), - "composer".to_string(), - ], - ]; - - self.git_util - .run_commands(commands, url, Some(&path), false, None)?; - } - - let command = vec![ - "git".to_string(), - "remote".to_string(), - "set-url".to_string(), - "composer".to_string(), - "--".to_string(), - "%sanitizedUrl%".to_string(), - ]; - self.git_util - .run_commands(vec![command], url, Some(&path), false, None)?; - - let pretty_version = target.get_pretty_version(); - if let Some(new_ref) = - self.update_to_commit(target.clone(), &path, &r#ref, &pretty_version)? - { - if target.get_dist_reference() == target.get_source_reference() { - // TODO(phase-b): set_dist_reference requires &mut PackageInterface - // target.set_dist_reference(Some(new_ref.clone())); - } - // target.set_source_reference(Some(new_ref)); - let _ = new_ref; - } - - let mut update_origin_url = false; - let mut output = String::new(); - if self.inner.process.borrow_mut().execute_args( - &vec!["git".to_string(), "remote".to_string(), "-v".to_string()], - &mut output, - Some(path.clone()), - ) == 0 - { - let mut origin_match: IndexMap<CaptureKey, String> = IndexMap::new(); - let mut composer_match: IndexMap<CaptureKey, String> = IndexMap::new(); - if Preg::is_match3( - r"{^origin\s+(?P<url>\S+)}m", - &output, - Some(&mut origin_match), - ) - .unwrap_or(false) - && Preg::is_match3( - r"{^composer\s+(?P<url>\S+)}m", - &output, - Some(&mut composer_match), - ) - .unwrap_or(false) - { - let origin_url = origin_match - .get(&CaptureKey::ByName("url".to_string())) - .cloned() - .unwrap_or_default(); - let composer_url = composer_match - .get(&CaptureKey::ByName("url".to_string())) - .cloned() - .unwrap_or_default(); - if origin_url == composer_url - && Some(composer_url.as_str()) != target.get_source_url().as_deref() - { - update_origin_url = true; - } - } - } - if update_origin_url && target.get_source_url().is_some() { - self.update_origin_url(&path, &target.get_source_url().unwrap()); - } - - Ok(None) - } - pub fn get_unpushed_changes( &self, _package: PackageInterfaceHandle, @@ -649,211 +255,6 @@ impl GitDownloader { Ok(unpushed_changes) } - pub(crate) async fn clean_changes( - &mut self, - package: PackageInterfaceHandle, - path: &str, - update: bool, - ) -> Result<Option<PhpMixed>> { - GitUtil::clean_env(&self.inner.process); - let path = self.normalize_path(path); - - let unpushed = self.get_unpushed_changes(package.clone(), &path)?; - if let Some(unpushed) = unpushed.as_deref() { - if self.inner.io.is_interactive() - || self - .inner - .config - .borrow_mut() - .get("discard-changes") - .as_bool() - != Some(true) - { - return Err(RuntimeException { - message: format!( - "Source directory {} has unpushed changes on the current branch: \n{}", - path, unpushed - ), - code: 0, - } - .into()); - } - } - - let changes = match self.get_local_changes(package.clone(), &path)? { - Some(c) => c, - None => return Ok(None), - }; - - if !self.inner.io.is_interactive() { - let discard_changes = self.inner.config.borrow_mut().get("discard-changes"); - if discard_changes.as_bool() == Some(true) { - return self.discard_changes(&path).await; - } - if discard_changes.as_string() == Some("stash") { - if !update { - return self - .inner - .clean_changes(package.clone(), &path, update) - .await; - } - - return self.stash_changes(&path).await; - } - - return self.inner.clean_changes(package, &path, update).await; - } - - let changes: Vec<String> = array_map( - |elem: &String| format!(" {}", elem), - &Preg::split(r"{\s*\r?\n\s*}", &changes)?, - ); - self.inner.io.write_error3( - &format!( - " <error>{} has modified files:</error>", - package.get_pretty_name() - ), - true, - io_interface::NORMAL, - ); - let slice_end = 10_usize.min(changes.len()); - // TODO(phase-b): PHP passes the list directly to writeError; joined here so write_error3 takes &str - self.inner - .io - .write_error3(&changes[..slice_end].join("\n"), true, io_interface::NORMAL); - if (changes.len() as i64) > 10 { - self.inner.io.write_error3( - &format!( - " <info>{} more files modified, choose \"v\" to view the full list</info>", - changes.len() as i64 - 10 - ), - true, - io_interface::NORMAL, - ); - } - - 'outer: loop { - let answer = self - .inner - .io - .ask( - format!( - " <info>Discard changes [y,n,v,{}?]?</info> ", - if update { "s," } else { "" } - ), - PhpMixed::String("?".to_string()), - ) - .as_string() - .map(|s| s.to_string()); - let mut do_help = false; - match answer.as_deref() { - Some("y") => { - self.discard_changes(&path).await?; - break 'outer; - } - Some("s") => { - if !update { - // goto help; - do_help = true; - } else { - self.stash_changes(&path).await?; - break 'outer; - } - } - Some("n") => { - return Err(RuntimeException { - message: "Update aborted".to_string(), - code: 0, - } - .into()); - } - Some("v") => { - // TODO(phase-b): PHP passes list directly; joined here for &str arg - self.inner - .io - .write_error3(&changes.join("\n"), true, io_interface::NORMAL); - } - Some("d") => { - self.view_diff(&path); - } - _ => { - // case '?': default: - do_help = true; - } - } - - if do_help { - // help: - // TODO(phase-b): PHP passes list directly; joined here for &str arg - self.inner.io.write_error3( - &[ - format!( - " y - discard changes and apply the {}", - if update { "update" } else { "uninstall" } - ), - format!( - " n - abort the {} and let you manually clean things up", - if update { "update" } else { "uninstall" } - ), - " v - view modified files".to_string(), - " d - view local modifications (diff)".to_string(), - ] - .join("\n"), - true, - io_interface::NORMAL, - ); - if update { - self.inner.io.write_error3( - " s - stash changes and try to reapply them after the update", - true, - io_interface::NORMAL, - ); - } - self.inner - .io - .write_error3(" ? - print help", true, io_interface::NORMAL); - } - } - - Ok(None) - } - - pub(crate) fn reapply_changes(&mut self, path: &str) -> Result<()> { - let path = self.normalize_path(path); - if self - .has_stashed_changes - .get(&path) - .copied() - .unwrap_or(false) - { - self.has_stashed_changes.shift_remove(&path); - self.inner.io.write_error3( - " <info>Re-applying stashed changes</info>", - true, - io_interface::NORMAL, - ); - let mut output = String::new(); - if self.inner.process.borrow_mut().execute_args( - &vec!["git".to_string(), "stash".to_string(), "pop".to_string()], - &mut output, - Some(path.clone()), - ) != 0 - { - return Err(RuntimeException { - message: format!( - "Failed to apply stashed changes:\n\n{}", - self.inner.process.borrow().get_error_output() - ), - code: 0, - } - .into()); - } - } - - self.has_discarded_changes.shift_remove(&path); - Ok(()) - } - /// Updates the given path to the given commit ref /// /// @throws \RuntimeException @@ -1153,42 +554,6 @@ impl GitDownloader { } } - pub(crate) fn get_commit_logs( - &mut self, - from_reference: &str, - to_reference: &str, - path: &str, - ) -> Result<String> { - let path = self.normalize_path(path); - let mut args = vec![ - "--format=%h - %an: %s".to_string(), - format!("{}..{}", from_reference, to_reference), - ]; - args.extend(GitUtil::get_no_show_signature_flags(&self.inner.process)); - let command = GitUtil::build_rev_list_command(&self.inner.process, args); - - let mut output = String::new(); - if self - .inner - .process - .borrow_mut() - .execute_args(&command, &mut output, Some(path.clone())) - != 0 - { - return Err(RuntimeException { - message: format!( - "Failed to execute {}\n\n{}", - implode(" ", &command), - self.inner.process.borrow().get_error_output(), - ), - code: 0, - } - .into()); - } - - Ok(GitUtil::parse_rev_list_output(&output, &self.inner.process)) - } - /// @phpstan-return PromiseInterface<void|null> /// @throws \RuntimeException pub(crate) async fn discard_changes(&mut self, path: &str) -> Result<Option<PhpMixed>> { @@ -1301,12 +666,6 @@ impl GitDownloader { path } - pub(crate) fn has_metadata_repository(&self, path: &str) -> bool { - let path = self.normalize_path(path); - - is_dir(&format!("{}/.git", path)) - } - pub(crate) fn get_short_hash(&self, reference: &str) -> String { if !self.inner.io.is_verbose() && Preg::is_match(r"{^[0-9a-f]{40}$}", reference).unwrap_or(false) @@ -1330,7 +689,7 @@ impl DvcsDownloaderInterface for GitDownloader { impl ChangeReportInterface for GitDownloader { fn get_local_changes( - &self, + &mut self, _package: PackageInterfaceHandle, path: &str, ) -> Result<Option<String>> { @@ -1379,13 +738,678 @@ impl VcsCapableDownloaderInterface for GitDownloader { } } -// TODO(phase-b): GitDownloader extends VcsDownloader which implements DownloaderInterface. -// Delegating each trait method to todo!() until the inner VcsDownloaderBase exposes the -// matching impl surface. +impl VcsDownloader for GitDownloader { + fn io(&self) -> std::rc::Rc<std::cell::RefCell<dyn IOInterface>> { + self.inner.io.clone() + } + + fn config(&self) -> &std::rc::Rc<std::cell::RefCell<Config>> { + &self.inner.config + } + + fn process(&self) -> &std::rc::Rc<std::cell::RefCell<ProcessExecutor>> { + &self.inner.process + } + + fn filesystem(&self) -> &std::rc::Rc<std::cell::RefCell<Filesystem>> { + &self.inner.filesystem + } + + fn has_cleaned_changes(&self) -> &IndexMap<String, bool> { + &self.inner.has_cleaned_changes + } + + fn has_cleaned_changes_mut(&mut self) -> &mut IndexMap<String, bool> { + &mut self.inner.has_cleaned_changes + } + + async fn do_download( + &mut self, + package: PackageInterfaceHandle, + path: &str, + url: &str, + prev_package: Option<PackageInterfaceHandle>, + ) -> Result<Option<PhpMixed>> { + // Do not create an extra local cache when repository is already local + if Filesystem::is_local_path(url) { + return Ok(None); + } + + GitUtil::clean_env(&self.inner.process); + + let cache_path = format!( + "{}/{}/", + self.inner + .config + .borrow_mut() + .get("cache-vcs-dir") + .as_string() + .unwrap_or(""), + Preg::replace(r"{[^a-z0-9.]}i", "-", &Url::sanitize(url.to_string()))?, + ); + let git_version = GitUtil::get_version(&self.inner.process); + + // --dissociate option is only available since git 2.3.0-rc0 + if git_version.is_some() + && version_compare(git_version.as_deref().unwrap_or(""), "2.3.0-rc0", ">=") + && Cache::is_usable(&cache_path) + { + self.inner.io.write_error3( + &format!( + " - Syncing <info>{}</info> (<comment>{}</comment>) into cache", + package.get_name(), + package + .get_full_pretty_version(true, crate::package::DisplayMode::SourceRefIfDev), + ), + true, + io_interface::NORMAL, + ); + self.inner.io.write_error3( + &sprintf( + " Cloning to cache at %s", + &[PhpMixed::String(cache_path.clone())], + ), + true, + io_interface::DEBUG, + ); + let r#ref = package.get_source_reference(); + let pretty_version = package.get_pretty_version(); + if self.git_util.fetch_ref_or_sync_mirror( + url, + &cache_path, + r#ref.as_deref().unwrap_or(""), + Some(&pretty_version), + )? && is_dir(&cache_path) + { + self.cached_packages + .entry(package.get_id()) + .or_insert_with(IndexMap::new) + .insert(r#ref.as_deref().unwrap_or("").to_string(), true); + } + } else if git_version.is_none() { + return Err(RuntimeException { + message: "git was not found in your PATH, skipping source download".to_string(), + code: 0, + } + .into()); + } + + Ok(None) + } + + async fn do_install( + &mut self, + package: PackageInterfaceHandle, + path: &str, + url: &str, + ) -> Result<Option<PhpMixed>> { + GitUtil::clean_env(&self.inner.process); + let path = self.normalize_path(path); + let cache_path = format!( + "{}/{}/", + self.inner + .config + .borrow_mut() + .get("cache-vcs-dir") + .as_string() + .unwrap_or(""), + Preg::replace(r"{[^a-z0-9.]}i", "-", &Url::sanitize(url.to_string()))?, + ); + let r#ref = package.get_source_reference().unwrap_or_default(); + + let msg; + let commands: Vec<Vec<String>>; + let has_cached = self + .cached_packages + .get(&package.get_id()) + .and_then(|m| m.get(&r#ref)) + .copied() + .unwrap_or(false); + if has_cached { + msg = format!("Cloning {} from cache", self.get_short_hash(&r#ref)); + + let mut clone_flags: Vec<String> = vec![ + "--dissociate".to_string(), + "--reference".to_string(), + cache_path.clone(), + ]; + let transport_options = package.get_transport_options(); + if let Some(git_opts) = transport_options.get("git").and_then(|v| v.as_array()) { + if let Some(single) = git_opts.get("single_use_clone").and_then(|v| v.as_bool()) { + if single { + clone_flags = vec![]; + } + } + } + + commands = vec![ + { + let mut base = vec![ + "git".to_string(), + "clone".to_string(), + "--no-checkout".to_string(), + cache_path.clone(), + path.clone(), + ]; + base.extend(clone_flags); + base + }, + vec![ + "git".to_string(), + "remote".to_string(), + "set-url".to_string(), + "origin".to_string(), + "--".to_string(), + "%sanitizedUrl%".to_string(), + ], + vec![ + "git".to_string(), + "remote".to_string(), + "add".to_string(), + "composer".to_string(), + "--".to_string(), + "%sanitizedUrl%".to_string(), + ], + ]; + } else { + msg = format!("Cloning {}", self.get_short_hash(&r#ref)); + commands = vec![ + vec![ + "git".to_string(), + "clone".to_string(), + "--no-checkout".to_string(), + "--".to_string(), + "%url%".to_string(), + path.clone(), + ], + vec![ + "git".to_string(), + "remote".to_string(), + "add".to_string(), + "composer".to_string(), + "--".to_string(), + "%url%".to_string(), + ], + vec![ + "git".to_string(), + "fetch".to_string(), + "composer".to_string(), + ], + vec![ + "git".to_string(), + "remote".to_string(), + "set-url".to_string(), + "origin".to_string(), + "--".to_string(), + "%sanitizedUrl%".to_string(), + ], + vec![ + "git".to_string(), + "remote".to_string(), + "set-url".to_string(), + "composer".to_string(), + "--".to_string(), + "%sanitizedUrl%".to_string(), + ], + ]; + if Platform::get_env("COMPOSER_DISABLE_NETWORK").is_some() { + return Err(RuntimeException { + message: format!( + "The required git reference for {} is not in cache and network is disabled, aborting", + package.get_name(), + ), + code: 0, + } + .into()); + } + } + + self.inner.io.write_error3(&msg, true, io_interface::NORMAL); + + self.git_util + .run_commands(commands, url, Some(&path), true, None)?; + + let source_url = package.get_source_url(); + if Some(url) != source_url.as_deref() && source_url.is_some() { + self.update_origin_url(&path, source_url.as_deref().unwrap()); + } else { + self.set_push_url(&path, url); + } + + let pretty_version = package.get_pretty_version(); + if let Some(new_ref) = + self.update_to_commit(package.clone(), &path, &r#ref, &pretty_version)? + { + if package.get_dist_reference() == package.get_source_reference() { + // TODO(phase-b): set_dist_reference requires &mut PackageInterface + // package.set_dist_reference(Some(new_ref.clone())); + } + // package.set_source_reference(Some(new_ref)); + let _ = new_ref; + } + + Ok(None) + } + + async fn do_update( + &mut self, + initial: PackageInterfaceHandle, + target: PackageInterfaceHandle, + path: &str, + url: &str, + ) -> Result<Option<PhpMixed>> { + GitUtil::clean_env(&self.inner.process); + let path = self.normalize_path(path); + if !self.has_metadata_repository(&path) { + return Err(RuntimeException { + message: format!( + "The .git directory is missing from {}, see https://getcomposer.org/commit-deps for more information", + path + ), + code: 0, + } + .into()); + } + + let cache_path = format!( + "{}/{}/", + self.inner + .config + .borrow_mut() + .get("cache-vcs-dir") + .as_string() + .unwrap_or(""), + Preg::replace(r"{[^a-z0-9.]}i", "-", &Url::sanitize(url.to_string()))?, + ); + let r#ref = target.get_source_reference().unwrap_or_default(); + + let msg; + let remote_url; + let has_cached = self + .cached_packages + .get(&target.get_id()) + .and_then(|m| m.get(&r#ref)) + .copied() + .unwrap_or(false); + if has_cached { + msg = format!("Checking out {} from cache", self.get_short_hash(&r#ref)); + remote_url = cache_path.clone(); + } else { + msg = format!("Checking out {}", self.get_short_hash(&r#ref)); + remote_url = "%url%".to_string(); + if Platform::get_env("COMPOSER_DISABLE_NETWORK").is_some() { + return Err(RuntimeException { + message: format!( + "The required git reference for {} is not in cache and network is disabled, aborting", + target.get_name(), + ), + code: 0, + } + .into()); + } + } + + self.inner.io.write_error3(&msg, true, io_interface::NORMAL); + + let mut output = String::new(); + if self.inner.process.borrow_mut().execute_args( + &vec![ + "git".to_string(), + "rev-parse".to_string(), + "--quiet".to_string(), + "--verify".to_string(), + format!("{}^{{commit}}", r#ref), + ], + &mut output, + Some(path.clone()), + ) != 0 + { + let commands = vec![ + vec![ + "git".to_string(), + "remote".to_string(), + "set-url".to_string(), + "composer".to_string(), + "--".to_string(), + remote_url.clone(), + ], + vec![ + "git".to_string(), + "fetch".to_string(), + "composer".to_string(), + ], + vec![ + "git".to_string(), + "fetch".to_string(), + "--tags".to_string(), + "composer".to_string(), + ], + ]; + + self.git_util + .run_commands(commands, url, Some(&path), false, None)?; + } + + let command = vec![ + "git".to_string(), + "remote".to_string(), + "set-url".to_string(), + "composer".to_string(), + "--".to_string(), + "%sanitizedUrl%".to_string(), + ]; + self.git_util + .run_commands(vec![command], url, Some(&path), false, None)?; + + let pretty_version = target.get_pretty_version(); + if let Some(new_ref) = + self.update_to_commit(target.clone(), &path, &r#ref, &pretty_version)? + { + if target.get_dist_reference() == target.get_source_reference() { + // TODO(phase-b): set_dist_reference requires &mut PackageInterface + // target.set_dist_reference(Some(new_ref.clone())); + } + // target.set_source_reference(Some(new_ref)); + let _ = new_ref; + } + + let mut update_origin_url = false; + let mut output = String::new(); + if self.inner.process.borrow_mut().execute_args( + &vec!["git".to_string(), "remote".to_string(), "-v".to_string()], + &mut output, + Some(path.clone()), + ) == 0 + { + let mut origin_match: IndexMap<CaptureKey, String> = IndexMap::new(); + let mut composer_match: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( + r"{^origin\s+(?P<url>\S+)}m", + &output, + Some(&mut origin_match), + ) + .unwrap_or(false) + && Preg::is_match3( + r"{^composer\s+(?P<url>\S+)}m", + &output, + Some(&mut composer_match), + ) + .unwrap_or(false) + { + let origin_url = origin_match + .get(&CaptureKey::ByName("url".to_string())) + .cloned() + .unwrap_or_default(); + let composer_url = composer_match + .get(&CaptureKey::ByName("url".to_string())) + .cloned() + .unwrap_or_default(); + if origin_url == composer_url + && Some(composer_url.as_str()) != target.get_source_url().as_deref() + { + update_origin_url = true; + } + } + } + if update_origin_url && target.get_source_url().is_some() { + self.update_origin_url(&path, &target.get_source_url().unwrap()); + } + + Ok(None) + } + + async fn clean_changes( + &mut self, + package: PackageInterfaceHandle, + path: &str, + update: bool, + ) -> Result<Option<PhpMixed>> { + GitUtil::clean_env(&self.inner.process); + let path = self.normalize_path(path); + + let unpushed = self.get_unpushed_changes(package.clone(), &path)?; + if let Some(unpushed) = unpushed.as_deref() { + if self.inner.io.is_interactive() + || self + .inner + .config + .borrow_mut() + .get("discard-changes") + .as_bool() + != Some(true) + { + return Err(RuntimeException { + message: format!( + "Source directory {} has unpushed changes on the current branch: \n{}", + path, unpushed + ), + code: 0, + } + .into()); + } + } + + let changes = match self.get_local_changes(package.clone(), &path)? { + Some(c) => c, + None => return Ok(None), + }; + + if !self.inner.io.is_interactive() { + let discard_changes = self.inner.config.borrow_mut().get("discard-changes"); + if discard_changes.as_bool() == Some(true) { + return self.discard_changes(&path).await; + } + if discard_changes.as_string() == Some("stash") { + if !update { + return self + .inner + .clean_changes(package.clone(), &path, update) + .await; + } + + return self.stash_changes(&path).await; + } + + return self.inner.clean_changes(package, &path, update).await; + } + + let changes: Vec<String> = array_map( + |elem: &String| format!(" {}", elem), + &Preg::split(r"{\s*\r?\n\s*}", &changes)?, + ); + self.inner.io.write_error3( + &format!( + " <error>{} has modified files:</error>", + package.get_pretty_name() + ), + true, + io_interface::NORMAL, + ); + let slice_end = 10_usize.min(changes.len()); + // TODO(phase-b): PHP passes the list directly to writeError; joined here so write_error3 takes &str + self.inner + .io + .write_error3(&changes[..slice_end].join("\n"), true, io_interface::NORMAL); + if (changes.len() as i64) > 10 { + self.inner.io.write_error3( + &format!( + " <info>{} more files modified, choose \"v\" to view the full list</info>", + changes.len() as i64 - 10 + ), + true, + io_interface::NORMAL, + ); + } + + 'outer: loop { + let answer = self + .inner + .io + .ask( + format!( + " <info>Discard changes [y,n,v,{}?]?</info> ", + if update { "s," } else { "" } + ), + PhpMixed::String("?".to_string()), + ) + .as_string() + .map(|s| s.to_string()); + let mut do_help = false; + match answer.as_deref() { + Some("y") => { + self.discard_changes(&path).await?; + break 'outer; + } + Some("s") => { + if !update { + // goto help; + do_help = true; + } else { + self.stash_changes(&path).await?; + break 'outer; + } + } + Some("n") => { + return Err(RuntimeException { + message: "Update aborted".to_string(), + code: 0, + } + .into()); + } + Some("v") => { + // TODO(phase-b): PHP passes list directly; joined here for &str arg + self.inner + .io + .write_error3(&changes.join("\n"), true, io_interface::NORMAL); + } + Some("d") => { + self.view_diff(&path); + } + _ => { + // case '?': default: + do_help = true; + } + } + + if do_help { + // help: + // TODO(phase-b): PHP passes list directly; joined here for &str arg + self.inner.io.write_error3( + &[ + format!( + " y - discard changes and apply the {}", + if update { "update" } else { "uninstall" } + ), + format!( + " n - abort the {} and let you manually clean things up", + if update { "update" } else { "uninstall" } + ), + " v - view modified files".to_string(), + " d - view local modifications (diff)".to_string(), + ] + .join("\n"), + true, + io_interface::NORMAL, + ); + if update { + self.inner.io.write_error3( + " s - stash changes and try to reapply them after the update", + true, + io_interface::NORMAL, + ); + } + self.inner + .io + .write_error3(" ? - print help", true, io_interface::NORMAL); + } + } + + Ok(None) + } + + fn reapply_changes(&mut self, path: &str) -> Result<()> { + let path = self.normalize_path(path); + if self + .has_stashed_changes + .get(&path) + .copied() + .unwrap_or(false) + { + self.has_stashed_changes.shift_remove(&path); + self.inner.io.write_error3( + " <info>Re-applying stashed changes</info>", + true, + io_interface::NORMAL, + ); + let mut output = String::new(); + if self.inner.process.borrow_mut().execute_args( + &vec!["git".to_string(), "stash".to_string(), "pop".to_string()], + &mut output, + Some(path.clone()), + ) != 0 + { + return Err(RuntimeException { + message: format!( + "Failed to apply stashed changes:\n\n{}", + self.inner.process.borrow().get_error_output() + ), + code: 0, + } + .into()); + } + } + + self.has_discarded_changes.shift_remove(&path); + Ok(()) + } + + fn get_commit_logs( + &mut self, + from_reference: &str, + to_reference: &str, + path: &str, + ) -> Result<String> { + let path = self.normalize_path(path); + let mut args = vec![ + "--format=%h - %an: %s".to_string(), + format!("{}..{}", from_reference, to_reference), + ]; + args.extend(GitUtil::get_no_show_signature_flags(&self.inner.process)); + let command = GitUtil::build_rev_list_command(&self.inner.process, args); + + let mut output = String::new(); + if self + .inner + .process + .borrow_mut() + .execute_args(&command, &mut output, Some(path.clone())) + != 0 + { + return Err(RuntimeException { + message: format!( + "Failed to execute {}\n\n{}", + implode(" ", &command), + self.inner.process.borrow().get_error_output(), + ), + code: 0, + } + .into()); + } + + Ok(GitUtil::parse_rev_list_output(&output, &self.inner.process)) + } + + fn has_metadata_repository(&self, path: &str) -> bool { + let path = self.normalize_path(path); + + is_dir(&format!("{}/.git", path)) + } +} + #[async_trait::async_trait(?Send)] impl crate::downloader::DownloaderInterface for GitDownloader { fn get_installation_source(&self) -> String { - todo!() + <Self as VcsDownloader>::get_installation_source(self) } fn as_dvcs_downloader_interface( @@ -1394,7 +1418,9 @@ impl crate::downloader::DownloaderInterface for GitDownloader { Some(self) } - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } @@ -1405,59 +1431,59 @@ impl crate::downloader::DownloaderInterface for GitDownloader { } async fn download( - &self, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, _output: bool, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::download(self, package, path, prev_package).await } async fn prepare( - &self, - _type: &str, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + r#type: &str, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::prepare(self, r#type, package, path, prev_package).await } async fn install( - &self, - _package: PackageInterfaceHandle, - _path: &str, + &mut self, + package: PackageInterfaceHandle, + path: &str, _output: bool, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::install(self, package, path).await } async fn update( - &self, - _initial: PackageInterfaceHandle, - _target: PackageInterfaceHandle, - _path: &str, + &mut self, + initial: PackageInterfaceHandle, + target: PackageInterfaceHandle, + path: &str, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::update(self, initial, target, path).await } async fn remove( - &self, - _package: PackageInterfaceHandle, - _path: &str, + &mut self, + package: PackageInterfaceHandle, + path: &str, _output: bool, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::remove(self, package, path).await } async fn cleanup( - &self, - _type: &str, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + r#type: &str, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::cleanup(self, r#type, package, path, prev_package).await } } diff --git a/crates/shirabe/src/downloader/gzip_downloader.rs b/crates/shirabe/src/downloader/gzip_downloader.rs index 337e298..0c47b67 100644 --- a/crates/shirabe/src/downloader/gzip_downloader.rs +++ b/crates/shirabe/src/downloader/gzip_downloader.rs @@ -132,7 +132,7 @@ impl GzipDownloader { impl ChangeReportInterface for GzipDownloader { fn get_local_changes( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, ) -> Result<Option<String>> { @@ -146,12 +146,14 @@ impl crate::downloader::DownloaderInterface for GzipDownloader { self.inner.get_installation_source() } - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } async fn download( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, prev_package: Option<PackageInterfaceHandle>, @@ -163,7 +165,7 @@ impl crate::downloader::DownloaderInterface for GzipDownloader { } async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, @@ -175,7 +177,7 @@ impl crate::downloader::DownloaderInterface for GzipDownloader { } async fn install( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -184,7 +186,7 @@ impl crate::downloader::DownloaderInterface for GzipDownloader { } async fn update( - &self, + &mut self, initial: PackageInterfaceHandle, target: PackageInterfaceHandle, path: &str, @@ -193,7 +195,7 @@ impl crate::downloader::DownloaderInterface for GzipDownloader { } async fn remove( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -202,7 +204,7 @@ impl crate::downloader::DownloaderInterface for GzipDownloader { } async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, diff --git a/crates/shirabe/src/downloader/hg_downloader.rs b/crates/shirabe/src/downloader/hg_downloader.rs index c5a65a0..a9a5bf8 100644 --- a/crates/shirabe/src/downloader/hg_downloader.rs +++ b/crates/shirabe/src/downloader/hg_downloader.rs @@ -4,6 +4,7 @@ use crate::config::Config; use crate::downloader::ChangeReportInterface; use crate::downloader::DownloaderInterface; use crate::downloader::VcsCapableDownloaderInterface; +use crate::downloader::VcsDownloader; use crate::downloader::VcsDownloaderBase; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; @@ -12,6 +13,7 @@ use crate::util::Filesystem; use crate::util::Hg as HgUtils; use crate::util::ProcessExecutor; use anyhow::Result; +use indexmap::IndexMap; use shirabe_php_shim::{PhpMixed, RuntimeException}; #[derive(Debug)] @@ -30,13 +32,39 @@ impl HgDownloader { inner: VcsDownloaderBase::new(io, config, Some(process), Some(fs)), } } +} - pub(crate) async fn do_download( - &self, - package: PackageInterfaceHandle, - path: String, - url: String, - prev_package: Option<PackageInterfaceHandle>, +impl VcsDownloader for HgDownloader { + fn io(&self) -> std::rc::Rc<std::cell::RefCell<dyn IOInterface>> { + self.inner.io.clone() + } + + fn config(&self) -> &std::rc::Rc<std::cell::RefCell<Config>> { + &self.inner.config + } + + fn process(&self) -> &std::rc::Rc<std::cell::RefCell<ProcessExecutor>> { + &self.inner.process + } + + fn filesystem(&self) -> &std::rc::Rc<std::cell::RefCell<Filesystem>> { + &self.inner.filesystem + } + + fn has_cleaned_changes(&self) -> &IndexMap<String, bool> { + &self.inner.has_cleaned_changes + } + + fn has_cleaned_changes_mut(&mut self) -> &mut IndexMap<String, bool> { + &mut self.inner.has_cleaned_changes + } + + async fn do_download( + &mut self, + _package: PackageInterfaceHandle, + _path: &str, + _url: &str, + _prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { if HgUtils::get_version(&self.inner.process).is_none() { return Err(RuntimeException { @@ -49,11 +77,11 @@ impl HgDownloader { Ok(None) } - pub(crate) async fn do_install( - &self, + async fn do_install( + &mut self, package: PackageInterfaceHandle, - path: String, - url: String, + path: &str, + url: &str, ) -> Result<Option<PhpMixed>> { let hg_utils = HgUtils::new( self.inner.io.clone(), @@ -61,7 +89,7 @@ impl HgDownloader { &self.inner.process, ); - let path_clone = path.clone(); + let path_clone = path.to_string(); let clone_command = move |url: String| -> Vec<String> { vec![ "hg".to_string(), @@ -71,7 +99,7 @@ impl HgDownloader { path_clone.clone(), ] }; - hg_utils.run_command(clone_command, url, Some(path.clone())); + hg_utils.run_command(clone_command, url.to_string(), Some(path.to_string())); let command = vec![ "hg".to_string(), @@ -86,7 +114,7 @@ impl HgDownloader { if self.inner.process.borrow_mut().execute_args( &command, &mut ignored_output, - shirabe_php_shim::realpath(&path), + shirabe_php_shim::realpath(path), ) != 0 { return Err(RuntimeException { @@ -103,12 +131,12 @@ impl HgDownloader { Ok(None) } - pub(crate) async fn do_update( - &self, - initial: PackageInterfaceHandle, + async fn do_update( + &mut self, + _initial: PackageInterfaceHandle, target: PackageInterfaceHandle, - path: String, - url: String, + path: &str, + url: &str, ) -> Result<Option<PhpMixed>> { let hg_utils = HgUtils::new( self.inner.io.clone(), @@ -125,7 +153,7 @@ impl HgDownloader { target.get_source_reference().unwrap_or_default() )); - if !self.has_metadata_repository(path.clone()) { + if !self.has_metadata_repository(path) { return Err(RuntimeException { message: format!( "The .hg directory is missing from {}, see https://getcomposer.org/commit-deps for more information", @@ -138,7 +166,7 @@ impl HgDownloader { let pull_command = |url: String| -> Vec<String> { vec!["hg".to_string(), "pull".to_string(), "--".to_string(), url] }; - hg_utils.run_command(pull_command, url.clone(), Some(path.clone())); + hg_utils.run_command(pull_command, url.to_string(), Some(path.to_string())); let ref_clone = ref_.clone(); let up_command = move |_url: String| -> Vec<String> { @@ -149,16 +177,16 @@ impl HgDownloader { ref_clone.clone(), ] }; - hg_utils.run_command(up_command, url, Some(path)); + hg_utils.run_command(up_command, url.to_string(), Some(path.to_string())); Ok(None) } - pub(crate) fn get_commit_logs( - &self, - from_reference: String, - to_reference: String, - path: String, + fn get_commit_logs( + &mut self, + from_reference: &str, + to_reference: &str, + path: &str, ) -> Result<String> { let command = vec![ "hg".to_string(), @@ -173,7 +201,7 @@ impl HgDownloader { if self.inner.process.borrow_mut().execute_args( &command, &mut output, - shirabe_php_shim::realpath(&path), + shirabe_php_shim::realpath(path), ) != 0 { return Err(RuntimeException { @@ -190,14 +218,14 @@ impl HgDownloader { Ok(output) } - pub(crate) fn has_metadata_repository(&self, path: String) -> bool { + fn has_metadata_repository(&self, path: &str) -> bool { std::path::Path::new(&format!("{}/.hg", path)).is_dir() } } impl ChangeReportInterface for HgDownloader { fn get_local_changes( - &self, + &mut self, _package: PackageInterfaceHandle, path: &str, ) -> Result<Option<String>> { @@ -228,12 +256,11 @@ impl VcsCapableDownloaderInterface for HgDownloader { } } -// TODO(phase-b): wire up VcsDownloader trait properly. HgDownloader extends VcsDownloader which -// implements DownloaderInterface in PHP. Delegating each trait method to todo!() until the inner -// VcsDownloaderBase exposes the matching impl surface. #[async_trait::async_trait(?Send)] impl DownloaderInterface for HgDownloader { - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } @@ -244,63 +271,63 @@ impl DownloaderInterface for HgDownloader { } fn get_installation_source(&self) -> String { - todo!() + <Self as VcsDownloader>::get_installation_source(self) } async fn download( - &self, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, _output: bool, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::download(self, package, path, prev_package).await } async fn prepare( - &self, - _type: &str, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + r#type: &str, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::prepare(self, r#type, package, path, prev_package).await } async fn install( - &self, - _package: PackageInterfaceHandle, - _path: &str, + &mut self, + package: PackageInterfaceHandle, + path: &str, _output: bool, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::install(self, package, path).await } async fn update( - &self, - _initial: PackageInterfaceHandle, - _target: PackageInterfaceHandle, - _path: &str, + &mut self, + initial: PackageInterfaceHandle, + target: PackageInterfaceHandle, + path: &str, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::update(self, initial, target, path).await } async fn remove( - &self, - _package: PackageInterfaceHandle, - _path: &str, + &mut self, + package: PackageInterfaceHandle, + path: &str, _output: bool, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::remove(self, package, path).await } async fn cleanup( - &self, - _type: &str, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + r#type: &str, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::cleanup(self, r#type, package, path, prev_package).await } } diff --git a/crates/shirabe/src/downloader/path_downloader.rs b/crates/shirabe/src/downloader/path_downloader.rs index 3eb2a48..99d72e7 100644 --- a/crates/shirabe/src/downloader/path_downloader.rs +++ b/crates/shirabe/src/downloader/path_downloader.rs @@ -537,7 +537,7 @@ impl VcsCapableDownloaderInterface for PathDownloader { impl crate::downloader::ChangeReportInterface for PathDownloader { fn get_local_changes( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, ) -> anyhow::Result<Option<String>> { @@ -555,7 +555,9 @@ impl DownloaderInterface for PathDownloader { self.inner.get_installation_source() } - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } @@ -566,7 +568,7 @@ impl DownloaderInterface for PathDownloader { } async fn download( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, prev_package: Option<PackageInterfaceHandle>, @@ -578,7 +580,7 @@ impl DownloaderInterface for PathDownloader { } async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, @@ -590,7 +592,7 @@ impl DownloaderInterface for PathDownloader { } async fn install( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -599,7 +601,7 @@ impl DownloaderInterface for PathDownloader { } async fn update( - &self, + &mut self, initial: PackageInterfaceHandle, target: PackageInterfaceHandle, path: &str, @@ -608,7 +610,7 @@ impl DownloaderInterface for PathDownloader { } async fn remove( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -617,7 +619,7 @@ impl DownloaderInterface for PathDownloader { } async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, diff --git a/crates/shirabe/src/downloader/perforce_downloader.rs b/crates/shirabe/src/downloader/perforce_downloader.rs index e33b159..2d91c60 100644 --- a/crates/shirabe/src/downloader/perforce_downloader.rs +++ b/crates/shirabe/src/downloader/perforce_downloader.rs @@ -4,6 +4,7 @@ use crate::config::Config; use crate::downloader::ChangeReportInterface; use crate::downloader::DownloaderInterface; use crate::downloader::VcsCapableDownloaderInterface; +use crate::downloader::VcsDownloader; use crate::downloader::VcsDownloaderBase; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; @@ -37,46 +38,6 @@ impl PerforceDownloader { } } - pub(crate) async fn do_download( - &self, - _package: PackageInterfaceHandle, - _path: String, - _url: String, - _prev_package: Option<PackageInterfaceHandle>, - ) -> Result<Option<PhpMixed>> { - Ok(None) - } - - pub async fn do_install( - &mut self, - package: PackageInterfaceHandle, - path: String, - url: String, - ) -> Result<Option<PhpMixed>> { - let source_ref = package.get_source_reference().map(|s| s.to_string()); - let label = self.get_label_from_source_reference(source_ref.clone().unwrap_or_default()); - - self.inner.io.write_error(&format!( - "Cloning {}", - source_ref.clone().unwrap_or_default() - )); - self.init_perforce(package, path.clone(), url); - self.perforce - .as_mut() - .unwrap() - .set_stream(&source_ref.clone().unwrap_or_default()); - self.perforce.as_mut().unwrap().p4_login(); - self.perforce.as_mut().unwrap().write_p4_client_spec(); - self.perforce.as_mut().unwrap().connect_client(); - self.perforce - .as_mut() - .unwrap() - .sync_code_base(label.as_deref()); - self.perforce.as_mut().unwrap().cleanup_client_spec(); - - Ok(None) - } - fn get_label_from_source_reference(&self, source_ref: String) -> Option<String> { let pos = source_ref.find('@'); if let Some(pos) = pos { @@ -116,42 +77,108 @@ impl PerforceDownloader { repository.get_repo_config().clone() } - pub(crate) async fn do_update( + pub fn set_perforce(&mut self, perforce: Perforce) { + self.perforce = Some(perforce); + } +} + +impl VcsDownloader for PerforceDownloader { + fn io(&self) -> std::rc::Rc<std::cell::RefCell<dyn IOInterface>> { + self.inner.io.clone() + } + + fn config(&self) -> &std::rc::Rc<std::cell::RefCell<Config>> { + &self.inner.config + } + + fn process(&self) -> &std::rc::Rc<std::cell::RefCell<ProcessExecutor>> { + &self.inner.process + } + + fn filesystem(&self) -> &std::rc::Rc<std::cell::RefCell<Filesystem>> { + &self.inner.filesystem + } + + fn has_cleaned_changes(&self) -> &IndexMap<String, bool> { + &self.inner.has_cleaned_changes + } + + fn has_cleaned_changes_mut(&mut self) -> &mut IndexMap<String, bool> { + &mut self.inner.has_cleaned_changes + } + + async fn do_download( + &mut self, + _package: PackageInterfaceHandle, + _path: &str, + _url: &str, + _prev_package: Option<PackageInterfaceHandle>, + ) -> Result<Option<PhpMixed>> { + Ok(None) + } + + async fn do_install( + &mut self, + package: PackageInterfaceHandle, + path: &str, + url: &str, + ) -> Result<Option<PhpMixed>> { + let source_ref = package.get_source_reference().map(|s| s.to_string()); + let label = self.get_label_from_source_reference(source_ref.clone().unwrap_or_default()); + + self.inner.io.write_error(&format!( + "Cloning {}", + source_ref.clone().unwrap_or_default() + )); + self.init_perforce(package, path.to_string(), url.to_string()); + self.perforce + .as_mut() + .unwrap() + .set_stream(&source_ref.clone().unwrap_or_default()); + self.perforce.as_mut().unwrap().p4_login(); + self.perforce.as_mut().unwrap().write_p4_client_spec(); + self.perforce.as_mut().unwrap().connect_client(); + self.perforce + .as_mut() + .unwrap() + .sync_code_base(label.as_deref()); + self.perforce.as_mut().unwrap().cleanup_client_spec(); + + Ok(None) + } + + async fn do_update( &mut self, _initial: PackageInterfaceHandle, target: PackageInterfaceHandle, - path: String, - url: String, + path: &str, + url: &str, ) -> Result<Option<PhpMixed>> { self.do_install(target, path, url).await } - pub(crate) fn get_commit_logs( + fn get_commit_logs( &mut self, - from_reference: String, - to_reference: String, - _path: String, + from_reference: &str, + to_reference: &str, + _path: &str, ) -> Result<String> { Ok(self .perforce .as_mut() .unwrap() - .get_commit_logs(&from_reference, &to_reference) + .get_commit_logs(from_reference, to_reference) .unwrap_or_default()) } - pub fn set_perforce(&mut self, perforce: Perforce) { - self.perforce = Some(perforce); - } - - pub(crate) fn has_metadata_repository(&self, _path: &str) -> bool { + fn has_metadata_repository(&self, _path: &str) -> bool { true } } impl ChangeReportInterface for PerforceDownloader { fn get_local_changes( - &self, + &mut self, _package: PackageInterfaceHandle, _path: &str, ) -> Result<Option<String>> { @@ -169,12 +196,11 @@ impl VcsCapableDownloaderInterface for PerforceDownloader { } } -// TODO(phase-b): wire up VcsDownloader trait properly. PerforceDownloader extends VcsDownloader -// which implements DownloaderInterface in PHP. Delegating each trait method to todo!() until the -// inner VcsDownloaderBase exposes the matching impl surface. #[async_trait::async_trait(?Send)] impl DownloaderInterface for PerforceDownloader { - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } @@ -185,63 +211,63 @@ impl DownloaderInterface for PerforceDownloader { } fn get_installation_source(&self) -> String { - todo!() + <Self as VcsDownloader>::get_installation_source(self) } async fn download( - &self, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, _output: bool, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::download(self, package, path, prev_package).await } async fn prepare( - &self, - _type: &str, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + r#type: &str, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::prepare(self, r#type, package, path, prev_package).await } async fn install( - &self, - _package: PackageInterfaceHandle, - _path: &str, + &mut self, + package: PackageInterfaceHandle, + path: &str, _output: bool, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::install(self, package, path).await } async fn update( - &self, - _initial: PackageInterfaceHandle, - _target: PackageInterfaceHandle, - _path: &str, + &mut self, + initial: PackageInterfaceHandle, + target: PackageInterfaceHandle, + path: &str, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::update(self, initial, target, path).await } async fn remove( - &self, - _package: PackageInterfaceHandle, - _path: &str, + &mut self, + package: PackageInterfaceHandle, + path: &str, _output: bool, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::remove(self, package, path).await } async fn cleanup( - &self, - _type: &str, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + r#type: &str, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::cleanup(self, r#type, package, path, prev_package).await } } diff --git a/crates/shirabe/src/downloader/phar_downloader.rs b/crates/shirabe/src/downloader/phar_downloader.rs index 46fdd84..8180764 100644 --- a/crates/shirabe/src/downloader/phar_downloader.rs +++ b/crates/shirabe/src/downloader/phar_downloader.rs @@ -66,7 +66,7 @@ impl PharDownloader { impl ChangeReportInterface for PharDownloader { fn get_local_changes( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, ) -> Result<Option<String>> { @@ -80,12 +80,14 @@ impl DownloaderInterface for PharDownloader { self.inner.get_installation_source() } - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } async fn download( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, prev_package: Option<PackageInterfaceHandle>, @@ -97,7 +99,7 @@ impl DownloaderInterface for PharDownloader { } async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, @@ -109,7 +111,7 @@ impl DownloaderInterface for PharDownloader { } async fn install( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -118,7 +120,7 @@ impl DownloaderInterface for PharDownloader { } async fn update( - &self, + &mut self, initial: PackageInterfaceHandle, target: PackageInterfaceHandle, path: &str, @@ -127,7 +129,7 @@ impl DownloaderInterface for PharDownloader { } async fn remove( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -136,7 +138,7 @@ impl DownloaderInterface for PharDownloader { } async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, diff --git a/crates/shirabe/src/downloader/rar_downloader.rs b/crates/shirabe/src/downloader/rar_downloader.rs index 4385bc3..5676a96 100644 --- a/crates/shirabe/src/downloader/rar_downloader.rs +++ b/crates/shirabe/src/downloader/rar_downloader.rs @@ -146,7 +146,7 @@ impl RarDownloader { impl ChangeReportInterface for RarDownloader { fn get_local_changes( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, ) -> Result<Option<String>> { @@ -160,12 +160,14 @@ impl crate::downloader::DownloaderInterface for RarDownloader { self.inner.get_installation_source() } - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } async fn download( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, prev_package: Option<PackageInterfaceHandle>, @@ -177,7 +179,7 @@ impl crate::downloader::DownloaderInterface for RarDownloader { } async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, @@ -189,7 +191,7 @@ impl crate::downloader::DownloaderInterface for RarDownloader { } async fn install( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -198,7 +200,7 @@ impl crate::downloader::DownloaderInterface for RarDownloader { } async fn update( - &self, + &mut self, initial: PackageInterfaceHandle, target: PackageInterfaceHandle, path: &str, @@ -207,7 +209,7 @@ impl crate::downloader::DownloaderInterface for RarDownloader { } async fn remove( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -216,7 +218,7 @@ impl crate::downloader::DownloaderInterface for RarDownloader { } async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, diff --git a/crates/shirabe/src/downloader/svn_downloader.rs b/crates/shirabe/src/downloader/svn_downloader.rs index 1e00c03..ef0fd16 100644 --- a/crates/shirabe/src/downloader/svn_downloader.rs +++ b/crates/shirabe/src/downloader/svn_downloader.rs @@ -9,6 +9,7 @@ use crate::config::Config; use crate::downloader::ChangeReportInterface; use crate::downloader::DownloaderInterface; use crate::downloader::VcsCapableDownloaderInterface; +use crate::downloader::VcsDownloader; use crate::downloader::VcsDownloaderBase; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; @@ -37,12 +38,85 @@ impl SvnDownloader { } } - pub(crate) async fn do_download( - &mut self, + pub(crate) fn execute( + &self, package: PackageInterfaceHandle, - path: &str, + base_url: &str, + command: Vec<String>, url: &str, - prev_package: Option<PackageInterfaceHandle>, + cwd: Option<&str>, + path: Option<&str>, + ) -> anyhow::Result<String> { + let mut util = SvnUtil::new( + base_url.to_string(), + self.inner.io.clone(), + self.inner.config.clone(), + Some(self.inner.process.clone()), + ); + util.set_cache_credentials(self.cache_credentials); + util.execute(command, url, cwd, path, self.inner.io.is_verbose()) + .map_err(|e| { + anyhow::anyhow!( + "{} could not be downloaded, {}", + package.get_pretty_name(), + e + ) + }) + } + + pub(crate) async fn discard_changes(&self, path: &str) -> anyhow::Result<Option<PhpMixed>> { + let mut output = String::new(); + if self.inner.process.borrow_mut().execute_args( + &["svn", "revert", "-R", "."].map(|s| s.to_string()).to_vec(), + &mut output, + Some(path.to_string()), + ) != 0 + { + return Err(RuntimeException { + message: format!( + "Could not reset changes\n\n:{}", + self.inner.process.borrow().get_error_output() + ), + code: 0, + } + .into()); + } + + Ok(None) + } +} + +impl VcsDownloader for SvnDownloader { + fn io(&self) -> std::rc::Rc<std::cell::RefCell<dyn IOInterface>> { + self.inner.io.clone() + } + + fn config(&self) -> &std::rc::Rc<std::cell::RefCell<Config>> { + &self.inner.config + } + + fn process(&self) -> &std::rc::Rc<std::cell::RefCell<ProcessExecutor>> { + &self.inner.process + } + + fn filesystem(&self) -> &std::rc::Rc<std::cell::RefCell<Filesystem>> { + &self.inner.filesystem + } + + fn has_cleaned_changes(&self) -> &IndexMap<String, bool> { + &self.inner.has_cleaned_changes + } + + fn has_cleaned_changes_mut(&mut self) -> &mut IndexMap<String, bool> { + &mut self.inner.has_cleaned_changes + } + + async fn do_download( + &mut self, + _package: PackageInterfaceHandle, + _path: &str, + url: &str, + _prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>> { SvnUtil::clean_env(); let mut util = SvnUtil::new( @@ -62,7 +136,7 @@ impl SvnDownloader { Ok(None) } - pub(crate) async fn do_install( + async fn do_install( &mut self, package: PackageInterfaceHandle, path: &str, @@ -109,9 +183,9 @@ impl SvnDownloader { Ok(None) } - pub(crate) async fn do_update( + async fn do_update( &mut self, - initial: PackageInterfaceHandle, + _initial: PackageInterfaceHandle, target: PackageInterfaceHandle, path: &str, url: &str, @@ -160,33 +234,7 @@ impl SvnDownloader { Ok(None) } - pub(crate) fn execute( - &self, - package: PackageInterfaceHandle, - base_url: &str, - command: Vec<String>, - url: &str, - cwd: Option<&str>, - path: Option<&str>, - ) -> anyhow::Result<String> { - let mut util = SvnUtil::new( - base_url.to_string(), - self.inner.io.clone(), - self.inner.config.clone(), - Some(self.inner.process.clone()), - ); - util.set_cache_credentials(self.cache_credentials); - util.execute(command, url, cwd, path, self.inner.io.is_verbose()) - .map_err(|e| { - anyhow::anyhow!( - "{} could not be downloaded, {}", - package.get_pretty_name(), - e - ) - }) - } - - pub(crate) async fn clean_changes( + async fn clean_changes( &mut self, package: PackageInterfaceHandle, path: &str, @@ -294,8 +342,8 @@ impl SvnDownloader { Ok(None) } - pub(crate) fn get_commit_logs( - &self, + fn get_commit_logs( + &mut self, from_reference: &str, to_reference: &str, path: &str, @@ -384,35 +432,14 @@ impl SvnDownloader { } } - pub(crate) async fn discard_changes(&self, path: &str) -> anyhow::Result<Option<PhpMixed>> { - let mut output = String::new(); - if self.inner.process.borrow_mut().execute_args( - &["svn", "revert", "-R", "."].map(|s| s.to_string()).to_vec(), - &mut output, - Some(path.to_string()), - ) != 0 - { - return Err(RuntimeException { - message: format!( - "Could not reset changes\n\n:{}", - self.inner.process.borrow().get_error_output() - ), - code: 0, - } - .into()); - } - - Ok(None) - } - - pub(crate) fn has_metadata_repository(&self, path: &str) -> bool { + fn has_metadata_repository(&self, path: &str) -> bool { is_dir(&format!("{}/.svn", path)) } } impl ChangeReportInterface for SvnDownloader { fn get_local_changes( - &self, + &mut self, _package: PackageInterfaceHandle, path: &str, ) -> anyhow::Result<Option<String>> { @@ -445,16 +472,15 @@ impl VcsCapableDownloaderInterface for SvnDownloader { } } -// TODO(phase-b): wire up VcsDownloader trait properly. SvnDownloader extends VcsDownloader which -// implements DownloaderInterface in PHP. Delegating each trait method to todo!() until the inner -// VcsDownloaderBase exposes the matching impl surface. #[async_trait::async_trait(?Send)] impl DownloaderInterface for SvnDownloader { fn get_installation_source(&self) -> String { - todo!() + <Self as VcsDownloader>::get_installation_source(self) } - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } @@ -465,59 +491,59 @@ impl DownloaderInterface for SvnDownloader { } async fn download( - &self, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, _output: bool, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::download(self, package, path, prev_package).await } async fn prepare( - &self, - _type: &str, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + r#type: &str, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::prepare(self, r#type, package, path, prev_package).await } async fn install( - &self, - _package: PackageInterfaceHandle, - _path: &str, + &mut self, + package: PackageInterfaceHandle, + path: &str, _output: bool, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::install(self, package, path).await } async fn update( - &self, - _initial: PackageInterfaceHandle, - _target: PackageInterfaceHandle, - _path: &str, + &mut self, + initial: PackageInterfaceHandle, + target: PackageInterfaceHandle, + path: &str, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::update(self, initial, target, path).await } async fn remove( - &self, - _package: PackageInterfaceHandle, - _path: &str, + &mut self, + package: PackageInterfaceHandle, + path: &str, _output: bool, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::remove(self, package, path).await } async fn cleanup( - &self, - _type: &str, - _package: PackageInterfaceHandle, - _path: &str, - _prev_package: Option<PackageInterfaceHandle>, + &mut self, + r#type: &str, + package: PackageInterfaceHandle, + path: &str, + prev_package: Option<PackageInterfaceHandle>, ) -> anyhow::Result<Option<PhpMixed>> { - todo!() + <Self as VcsDownloader>::cleanup(self, r#type, package, path, prev_package).await } } diff --git a/crates/shirabe/src/downloader/tar_downloader.rs b/crates/shirabe/src/downloader/tar_downloader.rs index 6cf1176..8835e7d 100644 --- a/crates/shirabe/src/downloader/tar_downloader.rs +++ b/crates/shirabe/src/downloader/tar_downloader.rs @@ -61,7 +61,7 @@ impl TarDownloader { impl ChangeReportInterface for TarDownloader { fn get_local_changes( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, ) -> Result<Option<String>> { @@ -75,12 +75,14 @@ impl DownloaderInterface for TarDownloader { self.inner.get_installation_source() } - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } async fn download( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, prev_package: Option<PackageInterfaceHandle>, @@ -92,7 +94,7 @@ impl DownloaderInterface for TarDownloader { } async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, @@ -104,7 +106,7 @@ impl DownloaderInterface for TarDownloader { } async fn install( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -113,7 +115,7 @@ impl DownloaderInterface for TarDownloader { } async fn update( - &self, + &mut self, initial: PackageInterfaceHandle, target: PackageInterfaceHandle, path: &str, @@ -122,7 +124,7 @@ impl DownloaderInterface for TarDownloader { } async fn remove( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -131,7 +133,7 @@ impl DownloaderInterface for TarDownloader { } async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, diff --git a/crates/shirabe/src/downloader/vcs_downloader.rs b/crates/shirabe/src/downloader/vcs_downloader.rs index 493440c..0637f08 100644 --- a/crates/shirabe/src/downloader/vcs_downloader.rs +++ b/crates/shirabe/src/downloader/vcs_downloader.rs @@ -93,13 +93,9 @@ pub trait VcsDownloader: DownloaderInterface + ChangeReportInterface + VcsCapableDownloaderInterface { fn io(&self) -> std::rc::Rc<std::cell::RefCell<dyn IOInterface>>; - fn io_mut(&mut self) -> &mut dyn IOInterface; fn config(&self) -> &std::rc::Rc<std::cell::RefCell<Config>>; - fn config_mut(&mut self) -> &mut std::rc::Rc<std::cell::RefCell<Config>>; fn process(&self) -> &std::rc::Rc<std::cell::RefCell<ProcessExecutor>>; - fn process_mut(&mut self) -> &mut std::rc::Rc<std::cell::RefCell<ProcessExecutor>>; fn filesystem(&self) -> &std::rc::Rc<std::cell::RefCell<Filesystem>>; - fn filesystem_mut(&mut self) -> &mut std::rc::Rc<std::cell::RefCell<Filesystem>>; fn has_cleaned_changes(&self) -> &IndexMap<String, bool>; fn has_cleaned_changes_mut(&mut self) -> &mut IndexMap<String, bool>; @@ -130,7 +126,12 @@ pub trait VcsDownloader: ) -> Result<Option<PhpMixed>>; /// Fetches the commit logs between two commits - fn get_commit_logs(&self, from_reference: &str, to_reference: &str, path: &str) -> String; + fn get_commit_logs( + &mut self, + from_reference: &str, + to_reference: &str, + path: &str, + ) -> Result<String>; /// Checks if VCS metadata repository has been initialized /// repository example: .git|.svn|.hg @@ -174,7 +175,7 @@ pub trait VcsDownloader: return Err(e); } if self.io().is_debug() { - self.io_mut().write_error3( + self.io().write_error3( &format!("Failed: [{}] {}", get_class_err(&e), e,), true, io_interface::NORMAL, @@ -185,7 +186,7 @@ pub trait VcsDownloader: .collect(), )) > 0 { - self.io_mut().write_error3( + self.io().write_error3( " Failed, trying the next URL", true, io_interface::NORMAL, @@ -219,9 +220,7 @@ pub trait VcsDownloader: self.has_cleaned_changes_mut() .insert(prev_package.unwrap().get_unique_name(), true); } else if r#type == "install" { - self.filesystem_mut() - .borrow_mut() - .empty_directory(path, true)?; + self.filesystem().borrow_mut().empty_directory(path, true)?; } else if r#type == "uninstall" { self.clean_changes(package, path, false).await?; } @@ -245,7 +244,7 @@ pub trait VcsDownloader: }) .unwrap_or(false) { - self.reapply_changes(path); + self.reapply_changes(path)?; self.has_cleaned_changes_mut() .shift_remove(&prev_package.unwrap().get_unique_name()); } @@ -269,7 +268,7 @@ pub trait VcsDownloader: .into()); } - self.io_mut().write_error3( + self.io().write_error3( &format!(" - {}: ", InstallOperation::format(package.clone(), false)), false, io_interface::NORMAL, @@ -290,7 +289,7 @@ pub trait VcsDownloader: return Err(e); } if self.io().is_debug() { - self.io_mut().write_error3( + self.io().write_error3( &format!("Failed: [{}] {}", get_class_err(&e), e,), true, io_interface::NORMAL, @@ -301,7 +300,7 @@ pub trait VcsDownloader: .collect(), )) > 0 { - self.io_mut().write_error3( + self.io().write_error3( " Failed, trying the next URL", true, io_interface::NORMAL, @@ -339,7 +338,7 @@ pub trait VcsDownloader: .into()); } - self.io_mut().write_error3( + self.io().write_error3( &format!( " - {}: ", UpdateOperation::format(initial.clone(), target.clone(), false), @@ -369,7 +368,7 @@ pub trait VcsDownloader: return Err(e); } if self.io().is_debug() { - self.io_mut().write_error3( + self.io().write_error3( &format!("Failed: [{}] {}", get_class_err(&e), e,), true, io_interface::NORMAL, @@ -380,7 +379,7 @@ pub trait VcsDownloader: .collect(), )) > 0 { - self.io_mut().write_error3( + self.io().write_error3( " Failed, trying the next URL", true, io_interface::NORMAL, @@ -397,11 +396,11 @@ pub trait VcsDownloader: let initial_ref = initial.get_source_reference().unwrap_or_default(); let target_ref = target.get_source_reference().unwrap_or_default(); let mut message = "Pulling in changes:"; - let mut logs = self.get_commit_logs(&initial_ref, &target_ref, path); + let mut logs = self.get_commit_logs(&initial_ref, &target_ref, path)?; if trim(&logs, None) == "" { message = "Rolling back changes:"; - logs = self.get_commit_logs(&target_ref, &initial_ref, path); + logs = self.get_commit_logs(&target_ref, &initial_ref, path)?; } if trim(&logs, None) != "" { @@ -414,10 +413,9 @@ pub trait VcsDownloader: // escape angle brackets for proper output in the console logs = str_replace("<", "\\<", &logs); - self.io_mut() + self.io() .write_error3(&format!(" {}", message), true, io_interface::NORMAL); - self.io_mut() - .write_error3(&logs, true, io_interface::NORMAL); + self.io().write_error3(&logs, true, io_interface::NORMAL); } } @@ -435,14 +433,14 @@ pub trait VcsDownloader: package: PackageInterfaceHandle, path: &str, ) -> Result<Option<PhpMixed>> { - self.io_mut().write_error3( + self.io().write_error3( &format!(" - {}", UninstallOperation::format(package, false)), true, io_interface::NORMAL, ); let result = self - .filesystem_mut() + .filesystem() .borrow_mut() .remove_directory_async(path) .await?; @@ -481,7 +479,7 @@ pub trait VcsDownloader: /// @param bool $update if true (update) the changes can be stashed and reapplied after an update, /// if false (remove) the changes should be assumed to be lost if the operation is not aborted async fn clean_changes( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, _update: bool, @@ -499,7 +497,9 @@ pub trait VcsDownloader: } /// Reapply previously stashed changes if applicable, only called after an update (regardless if successful or not) - fn reapply_changes(&self, _path: &str) {} + fn reapply_changes(&mut self, _path: &str) -> Result<()> { + Ok(()) + } fn prepare_urls(&self, mut urls: Vec<String>) -> Vec<String> { for index in 0..urls.len() { diff --git a/crates/shirabe/src/downloader/xz_downloader.rs b/crates/shirabe/src/downloader/xz_downloader.rs index 976497a..10a7edf 100644 --- a/crates/shirabe/src/downloader/xz_downloader.rs +++ b/crates/shirabe/src/downloader/xz_downloader.rs @@ -80,7 +80,7 @@ impl XzDownloader { impl ChangeReportInterface for XzDownloader { fn get_local_changes( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, ) -> Result<Option<String>> { @@ -94,12 +94,14 @@ impl crate::downloader::DownloaderInterface for XzDownloader { self.inner.get_installation_source() } - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } async fn download( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, prev_package: Option<PackageInterfaceHandle>, @@ -111,7 +113,7 @@ impl crate::downloader::DownloaderInterface for XzDownloader { } async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, @@ -123,7 +125,7 @@ impl crate::downloader::DownloaderInterface for XzDownloader { } async fn install( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -132,7 +134,7 @@ impl crate::downloader::DownloaderInterface for XzDownloader { } async fn update( - &self, + &mut self, initial: PackageInterfaceHandle, target: PackageInterfaceHandle, path: &str, @@ -141,7 +143,7 @@ impl crate::downloader::DownloaderInterface for XzDownloader { } async fn remove( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -150,7 +152,7 @@ impl crate::downloader::DownloaderInterface for XzDownloader { } async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, diff --git a/crates/shirabe/src/downloader/zip_downloader.rs b/crates/shirabe/src/downloader/zip_downloader.rs index 3fda248..90772ce 100644 --- a/crates/shirabe/src/downloader/zip_downloader.rs +++ b/crates/shirabe/src/downloader/zip_downloader.rs @@ -556,7 +556,7 @@ impl ZipDownloader { impl ChangeReportInterface for ZipDownloader { fn get_local_changes( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, ) -> Result<Option<String>> { @@ -573,12 +573,14 @@ impl crate::downloader::DownloaderInterface for ZipDownloader { self.inner.get_installation_source() } - fn as_change_report_interface(&self) -> Option<&dyn crate::downloader::ChangeReportInterface> { + fn as_change_report_interface( + &mut self, + ) -> Option<&mut dyn crate::downloader::ChangeReportInterface> { Some(self) } async fn download( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, prev_package: Option<PackageInterfaceHandle>, @@ -590,7 +592,7 @@ impl crate::downloader::DownloaderInterface for ZipDownloader { } async fn prepare( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, @@ -602,7 +604,7 @@ impl crate::downloader::DownloaderInterface for ZipDownloader { } async fn install( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -611,7 +613,7 @@ impl crate::downloader::DownloaderInterface for ZipDownloader { } async fn update( - &self, + &mut self, initial: PackageInterfaceHandle, target: PackageInterfaceHandle, path: &str, @@ -620,7 +622,7 @@ impl crate::downloader::DownloaderInterface for ZipDownloader { } async fn remove( - &self, + &mut self, package: PackageInterfaceHandle, path: &str, output: bool, @@ -629,7 +631,7 @@ impl crate::downloader::DownloaderInterface for ZipDownloader { } async fn cleanup( - &self, + &mut self, r#type: &str, package: PackageInterfaceHandle, path: &str, diff --git a/crates/shirabe/src/factory.rs b/crates/shirabe/src/factory.rs index 24ac2e0..90d8593 100644 --- a/crates/shirabe/src/factory.rs +++ b/crates/shirabe/src/factory.rs @@ -1011,52 +1011,52 @@ impl Factory { dm.set_downloader( "git", - Box::new(GitDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(GitDownloader::new( io.clone(), config.clone(), Some(process.clone()), Some(fs.clone()), - )), + ))), ); dm.set_downloader( "svn", - Box::new(SvnDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(SvnDownloader::new( io.clone(), config.clone(), process.clone(), fs.clone(), - )), + ))), ); dm.set_downloader( "fossil", - Box::new(FossilDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(FossilDownloader::new( io.clone(), config.clone(), process.clone(), fs.clone(), - )), + ))), ); dm.set_downloader( "hg", - Box::new(HgDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(HgDownloader::new( io.clone(), config.clone(), process.clone(), fs.clone(), - )), + ))), ); dm.set_downloader( "perforce", - Box::new(PerforceDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(PerforceDownloader::new( io.clone(), config.clone(), process.clone(), fs.clone(), - )), + ))), ); dm.set_downloader( "zip", - Box::new(ZipDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(ZipDownloader::new( io.clone(), config.clone(), http_downloader.clone(), @@ -1064,11 +1064,11 @@ impl Factory { cache.clone(), fs.clone(), process.clone(), - )), + ))), ); dm.set_downloader( "rar", - Box::new(RarDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(RarDownloader::new( io.clone(), config.clone(), http_downloader.clone(), @@ -1076,11 +1076,11 @@ impl Factory { cache.clone(), fs.clone(), process.clone(), - )), + ))), ); dm.set_downloader( "tar", - Box::new(TarDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(TarDownloader::new( io.clone(), config.clone(), http_downloader.clone(), @@ -1088,11 +1088,11 @@ impl Factory { cache.clone(), fs.clone(), process.clone(), - )), + ))), ); dm.set_downloader( "gzip", - Box::new(GzipDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(GzipDownloader::new( io.clone(), config.clone(), http_downloader.clone(), @@ -1100,11 +1100,11 @@ impl Factory { cache.clone(), fs.clone(), process.clone(), - )), + ))), ); dm.set_downloader( "xz", - Box::new(XzDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(XzDownloader::new( io.clone(), config.clone(), http_downloader.clone(), @@ -1112,11 +1112,11 @@ impl Factory { cache.clone(), fs.clone(), process.clone(), - )), + ))), ); dm.set_downloader( "phar", - Box::new(PharDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(PharDownloader::new( io.clone(), config.clone(), http_downloader.clone(), @@ -1124,11 +1124,11 @@ impl Factory { cache.clone(), fs.clone(), process.clone(), - )), + ))), ); dm.set_downloader( "file", - Box::new(FileDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(FileDownloader::new( io.clone(), config.clone(), http_downloader.clone(), @@ -1136,11 +1136,11 @@ impl Factory { cache.clone(), Some(fs.clone()), Some(process.clone()), - )), + ))), ); dm.set_downloader( "path", - Box::new(PathDownloader::new( + std::rc::Rc::new(std::cell::RefCell::new(PathDownloader::new( io.clone(), config.clone(), http_downloader.clone(), @@ -1148,7 +1148,7 @@ impl Factory { cache.clone(), fs.clone(), process.clone(), - )), + ))), ); Ok(std::rc::Rc::new(std::cell::RefCell::new(dm))) diff --git a/crates/shirabe/src/util/sync_helper.rs b/crates/shirabe/src/util/sync_helper.rs index 4181b23..6220f5c 100644 --- a/crates/shirabe/src/util/sync_helper.rs +++ b/crates/shirabe/src/util/sync_helper.rs @@ -8,7 +8,7 @@ use anyhow::Result; use shirabe_php_shim::PhpMixed; pub enum DownloaderOrManager<'a> { - Interface(&'a dyn DownloaderInterface), + Interface(&'a std::rc::Rc<std::cell::RefCell<dyn DownloaderInterface>>), Manager(&'a std::rc::Rc<std::cell::RefCell<DownloadManager>>), } @@ -20,7 +20,7 @@ impl<'a> DownloaderOrManager<'a> { prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { match self { - Self::Interface(d) => d.download3(package, path, prev_package).await, + Self::Interface(d) => d.borrow_mut().download3(package, path, prev_package).await, Self::Manager(d) => d.borrow().download(package, path, prev_package).await, } } @@ -33,7 +33,11 @@ impl<'a> DownloaderOrManager<'a> { prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { match self { - Self::Interface(d) => d.prepare(r#type, package, path, prev_package).await, + Self::Interface(d) => { + d.borrow_mut() + .prepare(r#type, package, path, prev_package) + .await + } Self::Manager(d) => { d.borrow() .prepare(r#type, package, path, prev_package) @@ -48,7 +52,7 @@ impl<'a> DownloaderOrManager<'a> { path: &str, ) -> Result<Option<PhpMixed>> { match self { - Self::Interface(d) => d.install2(package, path).await, + Self::Interface(d) => d.borrow_mut().install2(package, path).await, Self::Manager(d) => d.borrow().install(package, path).await, } } @@ -60,7 +64,7 @@ impl<'a> DownloaderOrManager<'a> { path: &str, ) -> Result<Option<PhpMixed>> { match self { - Self::Interface(d) => d.update(package, prev_package, path).await, + Self::Interface(d) => d.borrow_mut().update(package, prev_package, path).await, Self::Manager(d) => d.borrow().update(package, prev_package, path).await, } } @@ -73,7 +77,11 @@ impl<'a> DownloaderOrManager<'a> { prev_package: Option<PackageInterfaceHandle>, ) -> Result<Option<PhpMixed>> { match self { - Self::Interface(d) => d.cleanup(r#type, package, path, prev_package).await, + Self::Interface(d) => { + d.borrow_mut() + .cleanup(r#type, package, path, prev_package) + .await + } Self::Manager(d) => { d.borrow() .cleanup(r#type, package, path, prev_package) |
