aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/src/util')
-rw-r--r--crates/shirabe/src/util/auth_helper.rs42
-rw-r--r--crates/shirabe/src/util/bitbucket.rs230
-rw-r--r--crates/shirabe/src/util/config_validator.rs12
-rw-r--r--crates/shirabe/src/util/filesystem.rs30
-rw-r--r--crates/shirabe/src/util/forgejo.rs23
-rw-r--r--crates/shirabe/src/util/git.rs383
-rw-r--r--crates/shirabe/src/util/github.rs253
-rw-r--r--crates/shirabe/src/util/gitlab.rs151
-rw-r--r--crates/shirabe/src/util/hg.rs36
-rw-r--r--crates/shirabe/src/util/http/curl_downloader.rs60
-rw-r--r--crates/shirabe/src/util/http/proxy_manager.rs16
-rw-r--r--crates/shirabe/src/util/http/response.rs2
-rw-r--r--crates/shirabe/src/util/http_downloader.rs44
-rw-r--r--crates/shirabe/src/util/loop.rs28
-rw-r--r--crates/shirabe/src/util/perforce.rs38
-rw-r--r--crates/shirabe/src/util/process_executor.rs66
-rw-r--r--crates/shirabe/src/util/remote_filesystem.rs145
-rw-r--r--crates/shirabe/src/util/svn.rs49
-rw-r--r--crates/shirabe/src/util/sync_helper.rs30
-rw-r--r--crates/shirabe/src/util/url.rs110
20 files changed, 945 insertions, 803 deletions
diff --git a/crates/shirabe/src/util/auth_helper.rs b/crates/shirabe/src/util/auth_helper.rs
index 384d7af..43e13ea 100644
--- a/crates/shirabe/src/util/auth_helper.rs
+++ b/crates/shirabe/src/util/auth_helper.rs
@@ -17,9 +17,10 @@ use crate::util::bitbucket::Bitbucket;
use crate::util::github::GitHub;
use crate::util::gitlab::GitLab;
+#[derive(Debug)]
pub struct AuthHelper {
pub(crate) io: Box<dyn IOInterface>,
- pub(crate) config: Config,
+ pub(crate) config: std::rc::Rc<std::cell::RefCell<Config>>,
/// @var array<string, string> Map of origins to message displayed
displayed_origin_authentications: IndexMap<String, String>,
/// @var array<string, bool> Map of URLs and whether they already retried with authentication from Bitbucket
@@ -40,7 +41,7 @@ pub enum StoreAuth {
}
impl AuthHelper {
- pub fn new(io: Box<dyn IOInterface>, config: Config) -> Self {
+ pub fn new(io: Box<dyn IOInterface>, config: std::rc::Rc<std::cell::RefCell<Config>>) -> Self {
Self {
io,
config,
@@ -53,7 +54,8 @@ impl AuthHelper {
pub fn store_auth(&self, origin: &str, store_auth: StoreAuth) -> Result<()> {
// TODO(phase-b): config.get_auth_config_source() and ConfigSource methods are stubs
let mut store: Option<()> = None;
- let config_source = self.config.get_auth_config_source();
+ let config = self.config.borrow();
+ let config_source = config.get_auth_config_source();
if matches!(store_auth, StoreAuth::Bool(true)) {
store = Some(());
} else if matches!(store_auth, StoreAuth::Prompt) {
@@ -120,7 +122,7 @@ impl AuthHelper {
) -> Result<PromptAuthResult> {
let mut store_auth: StoreAuth = StoreAuth::Bool(false);
- let github_domains = self.config.get("github-domains");
+ let github_domains = self.config.borrow_mut().get("github-domains");
let github_domain_list = match github_domains.as_array() {
Some(arr) => arr.clone(),
None => IndexMap::new(),
@@ -129,7 +131,7 @@ impl AuthHelper {
.values()
.any(|v| v.as_string() == Some(origin));
- let gitlab_domains = self.config.get("gitlab-domains");
+ let gitlab_domains = self.config.borrow_mut().get("gitlab-domains");
let gitlab_domain_list = match gitlab_domains.as_array() {
Some(arr) => arr.clone(),
None => IndexMap::new(),
@@ -155,7 +157,7 @@ impl AuthHelper {
let sso_url = git_hub_util.get_sso_url(&headers);
message = format!(
"GitHub API token requires SSO authorization. Authorize this token at {}\n",
- sso_url,
+ sso_url.as_deref().unwrap_or(""),
);
self.io.write_error3(&message, true, io_interface::NORMAL);
if !self.io.is_interactive() {
@@ -231,7 +233,7 @@ impl AuthHelper {
if !git_hub_util.authorize_oauth(origin)
&& (!self.io.is_interactive()
- || !git_hub_util.authorize_oauth_interactively(origin, &message))
+ || !git_hub_util.authorize_oauth_interactively(origin, Some(&message))?)
{
return Err(TransportException::new(
format!("Could not authenticate against {}", origin),
@@ -289,8 +291,8 @@ impl AuthHelper {
|| !git_lab_util.authorize_oauth_interactively(
scheme.as_string().unwrap_or(""),
origin,
- &message,
- ))
+ Some(&message),
+ )?)
{
return Err(TransportException::new(
format!("Could not authenticate against {}", origin),
@@ -379,7 +381,8 @@ impl AuthHelper {
)?;
if !bit_bucket_util.authorize_oauth(&origin)
&& (!self.io.is_interactive()
- || !bit_bucket_util.authorize_oauth_interactively(&origin, &message))
+ || !bit_bucket_util
+ .authorize_oauth_interactively(&origin, Some(&message))?)
{
return Err(TransportException::new(
format!("Could not authenticate against {}", origin),
@@ -442,11 +445,8 @@ impl AuthHelper {
.into());
}
- self.io.write_error(
- PhpMixed::String(format!(
- " Authentication required (<info>{}</info>):",
- origin,
- )),
+ self.io.write_error3(
+ &format!(" Authentication required (<info>{}</info>):", origin,),
true,
io_interface::NORMAL,
);
@@ -459,7 +459,7 @@ impl AuthHelper {
);
// PHP: $this->config->get('store-auths') returns 'prompt'|bool
// TODO(phase-b): decode the PhpMixed result into StoreAuth
- store_auth = match self.config.get("store-auths") {
+ store_auth = match self.config.borrow_mut().get("store-auths") {
PhpMixed::Bool(b) => StoreAuth::Bool(b),
PhpMixed::String(ref s) if s == "prompt" => StoreAuth::Prompt,
_ => StoreAuth::Bool(false),
@@ -589,7 +589,7 @@ impl AuthHelper {
}
} else if origin == "github.com" && password == "x-oauth-basic" {
// only add the access_token if it is actually a github API URL
- if Preg::is_match(r"{^https?://api\.github\.com/}", url) {
+ if Preg::is_match(r"{^https?://api\.github\.com/}", url)? {
headers.push(PhpMixed::String(format!(
"Authorization: token {}",
username,
@@ -609,6 +609,7 @@ impl AuthHelper {
PhpMixed::String(origin.to_string()),
&PhpMixed::List(
self.config
+ .borrow_mut()
.get("gitlab-domains")
.as_array()
.map(|a| a.values().cloned().collect())
@@ -668,11 +669,8 @@ impl AuthHelper {
let already_displayed =
self.displayed_origin_authentications.get(origin) == Some(display_message);
if !already_displayed {
- self.io.write_error(
- PhpMixed::String(display_message.clone()),
- true,
- io_interface::DEBUG,
- );
+ self.io
+ .write_error3(display_message, true, io_interface::DEBUG);
self.displayed_origin_authentications
.insert(origin.to_string(), display_message.clone());
}
diff --git a/crates/shirabe/src/util/bitbucket.rs b/crates/shirabe/src/util/bitbucket.rs
index ca9aabd..cd4a9fa 100644
--- a/crates/shirabe/src/util/bitbucket.rs
+++ b/crates/shirabe/src/util/bitbucket.rs
@@ -12,12 +12,16 @@ use crate::io::io_interface::IOInterface;
use crate::util::http_downloader::HttpDownloader;
use crate::util::process_executor::ProcessExecutor;
+fn transport_error_code(err: &anyhow::Error) -> Option<i64> {
+ err.downcast_ref::<TransportException>().map(|te| te.code)
+}
+
#[derive(Debug)]
pub struct Bitbucket {
io: Box<dyn IOInterface>,
- config: Config,
- process: ProcessExecutor,
- http_downloader: HttpDownloader,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
+ process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>,
token: Option<IndexMap<String, PhpMixed>>,
time: Option<i64>,
}
@@ -28,15 +32,23 @@ impl Bitbucket {
pub fn new(
io: Box<dyn IOInterface>,
- config: Config,
- process: Option<ProcessExecutor>,
- http_downloader: Option<HttpDownloader>,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
+ process: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>,
+ http_downloader: Option<std::rc::Rc<std::cell::RefCell<HttpDownloader>>>,
time: Option<i64>,
) -> anyhow::Result<Self> {
- let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io));
+ let process = process.unwrap_or_else(|| {
+ std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(Some(
+ io.clone_box(),
+ ))))
+ });
let http_downloader = match http_downloader {
Some(h) => h,
- None => Factory::create_http_downloader(&*io, &config, IndexMap::new())?,
+ None => std::rc::Rc::new(std::cell::RefCell::new(Factory::create_http_downloader(
+ &*io,
+ &config,
+ IndexMap::new(),
+ )?)),
};
Ok(Self {
io,
@@ -64,21 +76,23 @@ impl Bitbucket {
return false;
}
- let mut output = String::new();
- if self.process.execute(
- &[
- "git".to_string(),
- "config".to_string(),
- "bitbucket.accesstoken".to_string(),
- ],
- &mut output,
- None,
- ) == 0
+ let mut output = PhpMixed::Null;
+ if self
+ .process
+ .borrow_mut()
+ .execute(
+ PhpMixed::from(vec!["git", "config", "bitbucket.accesstoken"]),
+ Some(&mut output),
+ None,
+ )
+ .unwrap_or(1)
+ == 0
{
+ let output_str = output.as_string().unwrap_or("").trim().to_string();
self.io.set_authentication(
origin_url.to_string(),
"x-token-auth".to_string(),
- Some(output.trim().to_string()),
+ Some(output_str),
);
return true;
}
@@ -104,68 +118,59 @@ impl Bitbucket {
Box::new(PhpMixed::Bool(false)),
);
options.insert("http".to_string(), Box::new(PhpMixed::Array(http)));
- let options = PhpMixed::Array(options);
+ let options: IndexMap<String, PhpMixed> =
+ options.into_iter().map(|(k, v)| (k, *v)).collect();
let response = match self
.http_downloader
- .get(Self::OAUTH2_ACCESS_TOKEN_URL, &options)
+ .borrow_mut()
+ .get(Self::OAUTH2_ACCESS_TOKEN_URL, options)
{
Ok(r) => r,
Err(te) => {
- if te.code == 400 {
- self.io.write_error(
- PhpMixed::String(
- "<error>Invalid OAuth consumer provided.</error>".to_string(),
- ),
+ let code = transport_error_code(&te).unwrap_or(0);
+ if code == 400 {
+ self.io.write_error3(
+ "<error>Invalid OAuth consumer provided.</error>",
+ true,
+ io_interface::NORMAL,
+ );
+ self.io.write_error3(
+ "This can have three reasons:",
+ true,
+ io_interface::NORMAL,
+ );
+ self.io.write_error3(
+ "1. You are authenticating with a bitbucket username/password combination",
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String("This can have three reasons:".to_string()),
+ self.io.write_error3(
+ "2. You are using an OAuth consumer, but didn't configure a (dummy) callback url",
+ true,
+ io_interface::NORMAL,
+ );
+ self.io.write_error3(
+ "3. You are using an OAuth consumer, but didn't configure it as private consumer",
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String(
- "1. You are authenticating with a bitbucket username/password combination".to_string(),
- ),
- true,
- io_interface::NORMAL,
- );
- self.io.write_error(
- PhpMixed::String(
- "2. You are using an OAuth consumer, but didn't configure a (dummy) callback url".to_string(),
- ),
- true,
- io_interface::NORMAL,
- );
- self.io.write_error(
- PhpMixed::String(
- "3. You are using an OAuth consumer, but didn't configure it as private consumer".to_string(),
- ),
- true,
- io_interface::NORMAL,
- );
return Ok(false);
}
- if te.code == 403 || te.code == 401 {
- self.io.write_error(
- PhpMixed::String(
- "<error>Invalid OAuth consumer provided.</error>".to_string(),
- ),
+ if code == 403 || code == 401 {
+ self.io.write_error3(
+ "<error>Invalid OAuth consumer provided.</error>",
+ true,
+ io_interface::NORMAL,
+ );
+ self.io.write_error3(
+ "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>\"",
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String(
- "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>\"".to_string(),
- ),
- true,
- io_interface::NORMAL,
- );
return Ok(false);
}
- return Err(te.into());
+ return Err(te);
}
};
@@ -204,43 +209,31 @@ impl Bitbucket {
message: Option<&str>,
) -> anyhow::Result<bool> {
if let Some(msg) = message {
- self.io.write_error(
- PhpMixed::String(msg.to_string()),
- true,
- io_interface::NORMAL,
- );
+ self.io.write_error3(msg, true, io_interface::NORMAL);
}
- let local_auth_config = self.config.get_local_auth_config_source();
+ let config_ref = self.config.borrow();
+ let local_auth_config = config_ref.get_local_auth_config_source();
let url =
"https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/";
- self.io.write_error(
- PhpMixed::String("Follow the instructions here:".to_string()),
- true,
- io_interface::NORMAL,
- );
- self.io.write_error(
- PhpMixed::String(url.to_string()),
- true,
- io_interface::NORMAL,
- );
- let auth_config_source_name = self.config.get_auth_config_source().get_name();
+ self.io
+ .write_error3("Follow the instructions here:", true, io_interface::NORMAL);
+ self.io.write_error3(url, true, io_interface::NORMAL);
+ let auth_config_source_name = config_ref.get_auth_config_source().get_name();
let local_name_prefix = local_auth_config
.as_ref()
.map(|c| format!("{} OR ", c.get_name()))
.unwrap_or_default();
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"to create a consumer. It will be stored in \"{}\" for future use by Composer.",
local_name_prefix + &auth_config_source_name
- )),
+ ),
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String(
- "Ensure you enter a \"Callback URL\" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)".to_string(),
- ),
+ self.io.write_error3(
+ "Ensure you enter a \"Callback URL\" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)",
true,
io_interface::NORMAL,
);
@@ -262,15 +255,13 @@ impl Bitbucket {
.to_string();
if consumer_key.is_empty() {
- self.io.write_error(
- PhpMixed::String("<warning>No consumer key given, aborting.</warning>".to_string()),
+ self.io.write_error3(
+ "<warning>No consumer key given, aborting.</warning>",
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String(
- "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>\"".to_string(),
- ),
+ self.io.write_error3(
+ "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>\"",
true,
io_interface::NORMAL,
);
@@ -285,17 +276,13 @@ impl Bitbucket {
.to_string();
if consumer_secret.is_empty() {
- self.io.write_error(
- PhpMixed::String(
- "<warning>No consumer secret given, aborting.</warning>".to_string(),
- ),
+ self.io.write_error3(
+ "<warning>No consumer secret given, aborting.</warning>",
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String(
- "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>\"".to_string(),
- ),
+ self.io.write_error3(
+ "You can also add it manually later by using \"composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>\"",
true,
io_interface::NORMAL,
);
@@ -312,10 +299,15 @@ impl Bitbucket {
return Ok(false);
}
- let use_local =
- store_in_local_auth_config && self.config.get_local_auth_config_source().is_some();
+ let use_local = store_in_local_auth_config
+ && self
+ .config
+ .borrow()
+ .get_local_auth_config_source()
+ .is_some();
if use_local {
- let mut auth_config_source = self.config.get_local_auth_config_source().unwrap();
+ let mut auth_config_source =
+ self.config.borrow().get_local_auth_config_source().unwrap();
self.store_in_auth_config(
&mut *auth_config_source,
origin_url,
@@ -323,7 +315,7 @@ impl Bitbucket {
&consumer_secret,
)?;
} else {
- let mut auth_config_source = self.config.get_auth_config_source();
+ let mut auth_config_source = self.config.borrow().get_auth_config_source();
self.store_in_auth_config(
&mut *auth_config_source,
origin_url,
@@ -333,11 +325,12 @@ impl Bitbucket {
}
self.config
+ .borrow()
.get_auth_config_source()
.remove_config_setting(&format!("http-basic.{}", origin_url))?;
- self.io.write_error(
- PhpMixed::String("<info>Consumer stored successfully.</info>".to_string()),
+ self.io.write_error3(
+ "<info>Consumer stored successfully.</info>",
true,
io_interface::NORMAL,
);
@@ -371,9 +364,14 @@ impl Bitbucket {
return Ok(String::new());
}
- let use_local = self.config.get_local_auth_config_source().is_some();
+ let use_local = self
+ .config
+ .borrow()
+ .get_local_auth_config_source()
+ .is_some();
if use_local {
- let mut auth_config_source = self.config.get_local_auth_config_source().unwrap();
+ let mut auth_config_source =
+ self.config.borrow().get_local_auth_config_source().unwrap();
self.store_in_auth_config(
&mut *auth_config_source,
origin_url,
@@ -381,7 +379,7 @@ impl Bitbucket {
consumer_secret,
)?;
} else {
- let mut auth_config_source = self.config.get_auth_config_source();
+ let mut auth_config_source = self.config.borrow().get_auth_config_source();
self.store_in_auth_config(
&mut *auth_config_source,
origin_url,
@@ -415,6 +413,7 @@ impl Bitbucket {
consumer_secret: &str,
) -> anyhow::Result<()> {
self.config
+ .borrow()
.get_config_source()
.remove_config_setting(&format!("bitbucket-oauth.{}", origin_url))?;
@@ -460,16 +459,19 @@ impl Bitbucket {
Box::new(PhpMixed::Int(t + expires_in)),
);
- self.config.get_auth_config_source().add_config_setting(
- &format!("bitbucket-oauth.{}", origin_url),
- PhpMixed::Array(consumer),
- )?;
+ self.config
+ .borrow()
+ .get_auth_config_source()
+ .add_config_setting(
+ &format!("bitbucket-oauth.{}", origin_url),
+ PhpMixed::Array(consumer),
+ )?;
Ok(())
}
fn get_token_from_config(&mut self, origin_url: &str) -> bool {
- let auth_config = self.config.get("bitbucket-oauth");
+ let auth_config = self.config.borrow_mut().get("bitbucket-oauth");
let auth_map = match auth_config.as_array() {
Some(m) => m.clone(),
diff --git a/crates/shirabe/src/util/config_validator.rs b/crates/shirabe/src/util/config_validator.rs
index 972191a..66fcadb 100644
--- a/crates/shirabe/src/util/config_validator.rs
+++ b/crates/shirabe/src/util/config_validator.rs
@@ -127,13 +127,17 @@ impl ConfigValidator {
let spdx_license = license_validator.get_license_by_identifier(license);
if let Some(spdx_license) = spdx_license {
if spdx_license[3] {
- if Preg::is_match(r"{^[AL]?GPL-[123](\.[01])?\+$}i", license) {
+ if Preg::is_match(r"{^[AL]?GPL-[123](\.[01])?\+$}i", license)
+ .unwrap_or(false)
+ {
warnings.push(format!(
"License \"{}\" is a deprecated SPDX license identifier, use \"{}-or-later\" instead",
license,
license.replace('+', "")
));
- } else if Preg::is_match(r"{^[AL]?GPL-[123](\.[01])?$}i", license) {
+ } else if Preg::is_match(r"{^[AL]?GPL-[123](\.[01])?$}i", license)
+ .unwrap_or(false)
+ {
warnings.push(format!(
"License \"{}\" is a deprecated SPDX license identifier, use \"{}-only\" or \"{}-or-later\" instead",
license, license, license
@@ -154,7 +158,7 @@ impl ConfigValidator {
}
if let Some(PhpMixed::String(name)) = manifest.get("name") {
- if !name.is_empty() && Preg::is_match(r"{[A-Z]}", name) {
+ if !name.is_empty() && Preg::is_match(r"{[A-Z]}", name).unwrap_or(false) {
let suggest_name = Preg::replace(
r"{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}",
r"\1\3-\2\4",
@@ -230,7 +234,7 @@ impl ConfigValidator {
packages.extend(require_dev);
for (package, version) in &packages {
if let PhpMixed::String(version_str) = version.as_ref() {
- if Preg::is_match(r"{#}", version_str) {
+ if Preg::is_match(r"{#}", version_str).unwrap_or(false) {
warnings.push(format!(
"The package \"{}\" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.",
package
diff --git a/crates/shirabe/src/util/filesystem.rs b/crates/shirabe/src/util/filesystem.rs
index ce2abf8..2ab08da 100644
--- a/crates/shirabe/src/util/filesystem.rs
+++ b/crates/shirabe/src/util/filesystem.rs
@@ -21,11 +21,11 @@ use crate::util::silencer::Silencer;
#[derive(Debug)]
pub struct Filesystem {
- process_executor: Option<ProcessExecutor>,
+ process_executor: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>,
}
impl Filesystem {
- pub fn new(executor: Option<ProcessExecutor>) -> Self {
+ pub fn new(executor: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>) -> Self {
Self {
process_executor: executor,
}
@@ -191,7 +191,7 @@ impl Filesystem {
return Ok(Some(true));
}
- if Preg::is_match("{^(?:[a-z]:)?[/\\\\]+$}i", directory, None).unwrap_or(false) {
+ if Preg::is_match3("{^(?:[a-z]:)?[/\\\\]+$}i", directory, None).unwrap_or(false) {
return Err(RuntimeException {
message: format!("Aborting an attempted deletion of {}, this was probably not intended, if it is a real use case please report it.", directory),
code: 0,
@@ -532,7 +532,7 @@ impl Filesystem {
let mut common_path = to.clone();
while strpos(&format!("{}/", from), &format!("{}/", common_path)) != Some(0)
&& "/" != common_path
- && !Preg::is_match("{^[A-Z]:/?$}i", &common_path, None).unwrap_or(false)
+ && !Preg::is_match3("{^[A-Z]:/?$}i", &common_path, None).unwrap_or(false)
{
common_path = strtr(&dirname(&common_path), "\\", "/");
}
@@ -545,7 +545,7 @@ impl Filesystem {
common_path = format!("{}/", rtrim(&common_path, "/"));
let source_path_depth =
substr_count(&substr(&from, strlen(&common_path) as isize, None), "/");
- let common_path_code = str_repeat("../", source_path_depth);
+ let common_path_code = str_repeat("../", source_path_depth as usize);
// allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups
if !prefer_relative && "/" == common_path && source_path_depth > 1 {
@@ -593,7 +593,7 @@ impl Filesystem {
let mut common_path = to.clone();
while strpos(&format!("{}/", from), &format!("{}/", common_path)) != Some(0)
&& "/" != common_path
- && !Preg::is_match("{^[A-Z]:/?$}i", &common_path, None).unwrap_or(false)
+ && !Preg::is_match3("{^[A-Z]:/?$}i", &common_path, None).unwrap_or(false)
&& "." != common_path
{
common_path = strtr(&dirname(&common_path), "\\", "/");
@@ -692,7 +692,7 @@ impl Filesystem {
// extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive:
let mut prefix_match: Vec<String> = vec![];
- if Preg::is_match_strict_groups(
+ if Preg::is_match_strict_groups3(
"{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix",
&path,
Some(&mut prefix_match),
@@ -734,7 +734,7 @@ impl Filesystem {
/// And other possible unforeseen disasters, see https://github.com/composer/composer/pull/9422
pub fn trim_trailing_slash(path: &str) -> String {
let mut path = path.to_string();
- if !Preg::is_match("{^[/\\\\]+$}", &path, None).unwrap_or(false) {
+ if !Preg::is_match3("{^[/\\\\]+$}", &path, None).unwrap_or(false) {
path = rtrim(&path, "/\\");
}
@@ -746,7 +746,7 @@ impl Filesystem {
// on windows, \\foo indicates network paths so we exclude those from local paths, however it is unsafe
// on linux as file:////foo (which would be a network path \\foo on windows) will resolve to /foo which could be a local path
if Platform::is_windows() {
- return Preg::is_match(
+ return Preg::is_match3(
"{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\\.\\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i",
path,
None,
@@ -754,7 +754,7 @@ impl Filesystem {
.unwrap_or(false);
}
- Preg::is_match(
+ Preg::is_match3(
"{^(file://|/|/?[a-z]:[\\\\/]|\\.\\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i",
path,
None,
@@ -809,12 +809,14 @@ impl Filesystem {
size
}
- pub(crate) fn get_process(&mut self) -> &mut ProcessExecutor {
+ pub(crate) fn get_process(&mut self) -> std::cell::RefMut<'_, ProcessExecutor> {
if self.process_executor.is_none() {
- self.process_executor = Some(ProcessExecutor::new(None));
+ self.process_executor = Some(std::rc::Rc::new(std::cell::RefCell::new(
+ ProcessExecutor::new(None),
+ )));
}
- self.process_executor.as_mut().unwrap()
+ self.process_executor.as_ref().unwrap().borrow_mut()
}
/// delete symbolic link implementation (commonly known as "unlink()")
@@ -960,7 +962,7 @@ impl Filesystem {
let stat = lstat(junction);
// S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask)
- if let Some(arr) = stat.as_array() {
+ if let Some(arr) = stat {
let mode = arr.get("mode").and_then(|v| v.as_int()).unwrap_or(0);
return 0x4000 != (mode & 0xF000);
}
diff --git a/crates/shirabe/src/util/forgejo.rs b/crates/shirabe/src/util/forgejo.rs
index 3aaff91..88df409 100644
--- a/crates/shirabe/src/util/forgejo.rs
+++ b/crates/shirabe/src/util/forgejo.rs
@@ -9,12 +9,16 @@ use crate::util::http_downloader::HttpDownloader;
#[derive(Debug)]
pub struct Forgejo {
io: Box<dyn IOInterface>,
- config: Config,
- http_downloader: HttpDownloader,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
+ http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>,
}
impl Forgejo {
- pub fn new(io: Box<dyn IOInterface>, config: Config, http_downloader: HttpDownloader) -> Self {
+ pub fn new(
+ io: Box<dyn IOInterface>,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
+ http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>,
+ ) -> Self {
Self {
io,
config,
@@ -39,7 +43,7 @@ impl Forgejo {
io_interface::NORMAL,
);
self.io.write_error3(&url, true, io_interface::NORMAL);
- let local_auth_config = self.config.get_local_auth_config_source();
+ let local_auth_config = self.config.borrow().get_local_auth_config_source();
self.io.write_error3(
&format!(
"Tokens will be stored in plain text in \"{}\" for future use by Composer.",
@@ -47,7 +51,7 @@ impl Forgejo {
.as_ref()
.map(|s| format!("{} OR ", s.get_name()))
.unwrap_or_default()
- + self.config.get_auth_config_source().get_name()
+ + self.config.borrow().get_auth_config_source().get_name()
),
true,
io_interface::NORMAL,
@@ -92,7 +96,7 @@ impl Forgejo {
self.io
.set_authentication(origin_url.to_string(), username.clone(), token.clone());
- match self.http_downloader.get(
+ match self.http_downloader.borrow_mut().get(
&format!("https://{}/api/v1/version", origin_url),
indexmap::indexmap! {
"retry-auth-failure".to_string() => false.into(),
@@ -117,15 +121,16 @@ impl Forgejo {
}
// store value in local/user config
- let local_auth_config = self.config.get_local_auth_config_source();
+ let local_auth_config = self.config.borrow().get_local_auth_config_source();
let auth_config_source = if store_in_local_auth_config {
local_auth_config
.as_ref()
- .unwrap_or_else(|| self.config.get_auth_config_source())
+ .unwrap_or_else(|| self.config.borrow().get_auth_config_source())
} else {
- self.config.get_auth_config_source()
+ self.config.borrow().get_auth_config_source()
};
self.config
+ .borrow()
.get_config_source()
.remove_config_setting(&format!("forgejo-token.{}", origin_url));
auth_config_source.add_config_setting(
diff --git a/crates/shirabe/src/util/git.rs b/crates/shirabe/src/util/git.rs
index 8fe66a4..3093734 100644
--- a/crates/shirabe/src/util/git.rs
+++ b/crates/shirabe/src/util/git.rs
@@ -5,7 +5,7 @@ use anyhow::Result;
use indexmap::IndexMap;
use std::sync::Mutex;
-use shirabe_external_packages::composer::pcre::preg::Preg;
+use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg};
use shirabe_php_shim::{
InvalidArgumentException, PHP_EOL, PhpMixed, RuntimeException, array_map,
array_merge_recursive, clearstatcache, count, explode, implode, in_array, is_array,
@@ -28,10 +28,10 @@ use crate::util::url::Url;
#[derive(Debug)]
pub struct Git {
pub(crate) io: Box<dyn IOInterface>,
- pub(crate) config: Config,
- pub(crate) process: ProcessExecutor,
- pub(crate) filesystem: Filesystem,
- pub(crate) http_downloader: Option<HttpDownloader>,
+ pub(crate) config: std::rc::Rc<std::cell::RefCell<Config>>,
+ pub(crate) process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ pub(crate) filesystem: std::rc::Rc<std::cell::RefCell<Filesystem>>,
+ pub(crate) http_downloader: Option<std::rc::Rc<std::cell::RefCell<HttpDownloader>>>,
}
/// @var string|false|null
@@ -40,9 +40,9 @@ static VERSION: Mutex<Option<Option<String>>> = Mutex::new(None);
impl Git {
pub fn new(
io: Box<dyn IOInterface>,
- config: Config,
- process: ProcessExecutor,
- fs: Filesystem,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
+ process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ fs: std::rc::Rc<std::cell::RefCell<Filesystem>>,
) -> Self {
Self {
io,
@@ -73,8 +73,8 @@ impl Git {
.into());
}
Some(io) => {
- io.write_error(
- PhpMixed::String(format!("<warning>{}</warning>", msg)),
+ io.write_error3(
+ &format!("<warning>{}</warning>", msg),
true,
io_interface::NORMAL,
);
@@ -84,7 +84,10 @@ impl Git {
Ok(())
}
- pub fn set_http_downloader(&mut self, http_downloader: HttpDownloader) {
+ pub fn set_http_downloader(
+ &mut self,
+ http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>,
+ ) {
self.http_downloader = Some(http_downloader);
}
@@ -114,7 +117,7 @@ impl Git {
map.insert("%url%".to_string(), url.to_string());
map.insert(
"%sanitizedUrl%".to_string(),
- Preg::replace(r"{://([^@]+?):(.+?)@}", "://", url.to_string()),
+ Preg::replace(r"{://([^@]+?):(.+?)@}", "://", &url),
);
array_map(
@@ -144,8 +147,11 @@ impl Git {
let mut last_command: PhpMixed = PhpMixed::String(String::new());
// Ensure we are allowed to use this URL by config
- self.config
- .prohibit_url_by_config(url, Some(self.io.as_ref()), &IndexMap::new())?;
+ self.config.borrow_mut().prohibit_url_by_config(
+ url,
+ Some(self.io.as_ref()),
+ &IndexMap::new(),
+ )?;
let orig_cwd: Option<String> = if initial_clone {
cwd.map(|s| s.to_string())
@@ -184,7 +190,7 @@ impl Git {
} else {
cwd_string.clone()
};
- status = this_process.execute(&cmd, &mut local_output, exec_cwd);
+ status = this_process.execute_args(&cmd, &mut local_output, exec_cwd);
if collect_outputs {
outputs.push(local_output);
}
@@ -217,36 +223,46 @@ impl Git {
if !initial_clone {
// capture username/password from URL if there is one and we have no auth configured yet
let mut output = String::new();
- self.process.execute(
+ self.process.borrow_mut().execute_args(
&vec!["git".to_string(), "remote".to_string(), "-v".to_string()],
&mut output,
cwd.map(|s| s.to_string()),
);
- if let Some(m) = Preg::is_match_strict_groups(
+ let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::is_match_strict_groups3(
r"{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im",
&output,
- ) {
- let m3 = m.get(3).cloned().unwrap_or_default();
+ Some(&mut m),
+ )
+ .unwrap_or(false)
+ {
+ let m3 = m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default();
if !self.io.has_authentication(&m3) {
self.io.set_authentication(
m3.clone(),
- rawurldecode(&m.get(1).cloned().unwrap_or_default()),
- Some(rawurldecode(&m.get(2).cloned().unwrap_or_default())),
+ rawurldecode(&m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default()),
+ Some(rawurldecode(
+ &m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
+ )),
);
}
}
}
- let protocols = self.config.get("github-protocols");
+ let protocols = self.config.borrow_mut().get("github-protocols");
// public github, autoswitch protocols
// @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
- if let Some(m) = Preg::is_match_strict_groups(
+ let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::is_match_strict_groups3(
&format!(
"{{^(?:https?|git)://{}/(.*)}}",
- Self::get_github_domains_regex(&self.config)
+ Self::get_github_domains_regex(&*self.config.borrow())
),
url,
- ) {
+ Some(&mut m),
+ )
+ .unwrap_or(false)
+ {
let mut messages: Vec<String> = vec![];
let protocols_list: Vec<String> = match &protocols {
PhpMixed::List(l) => l
@@ -256,8 +272,8 @@ impl Git {
_ => vec![],
};
for protocol in &protocols_list {
- let m1 = m.get(1).cloned().unwrap_or_default();
- let m2 = m.get(2).cloned().unwrap_or_default();
+ let m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default();
+ let m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default();
let proto_url = if protocol == "ssh" {
format!("git@{}:{}", m1, m2)
} else {
@@ -266,7 +282,7 @@ impl Git {
if run_commands_inline(
&proto_url,
- &mut self.process,
+ &mut *self.process.borrow_mut(),
&mut last_command,
command_output.as_deref_mut(),
) == 0
@@ -276,12 +292,17 @@ impl Git {
messages.push(format!(
"- {}\n{}",
proto_url,
- Preg::replace(r"#^#m", " ", self.process.get_error_output().to_string())
+ Preg::replace(
+ r"#^#m",
+ " ",
+ &self.process.borrow().get_error_output().to_string()
+ )
+ .unwrap_or_default()
));
if initial_clone {
if let Some(ref orig) = orig_cwd {
- self.filesystem.remove_directory(orig);
+ self.filesystem.borrow_mut().remove_directory(orig);
}
}
}
@@ -302,7 +323,7 @@ impl Git {
}
// if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https
- let protocols_list: Vec<String> = match self.config.get("github-protocols") {
+ let protocols_list: Vec<String> = match self.config.borrow_mut().get("github-protocols") {
PhpMixed::List(l) => l
.iter()
.filter_map(|v| v.as_string().map(|s| s.to_string()))
@@ -312,7 +333,7 @@ impl Git {
let bypass_ssh_for_github = Preg::is_match(
&format!(
"{{^git@{}:(.+?)\\.git$}}i",
- Self::get_github_domains_regex(&self.config)
+ Self::get_github_domains_regex(&*self.config.borrow())
),
url,
)
@@ -333,40 +354,43 @@ impl Git {
if bypass_ssh_for_github
|| 0 != run_commands_inline(
url,
- &mut self.process,
+ &mut *self.process.borrow_mut(),
&mut last_command,
command_output.as_deref_mut(),
)
{
- let mut error_msg = self.process.get_error_output().to_string();
+ let mut error_msg = self.process.borrow().get_error_output().to_string();
// private github repository without ssh key access, try https with auth
// @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
- let github_ssh_match = Preg::is_match_strict_groups(
+ let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
+ let github_matched = Preg::is_match_strict_groups3(
&format!(
"{{^git@{}:(.+?)\\.git$}}i",
- Self::get_github_domains_regex(&self.config)
+ Self::get_github_domains_regex(&*self.config.borrow())
),
url,
- );
- let github_https_match = Preg::is_match_strict_groups(
- &format!(
- "{{^https?://{}/(.*?)(?:\\.git)?$}}i",
- Self::get_github_domains_regex(&self.config)
- ),
- url,
- );
- if let Some(m) = github_ssh_match.or(github_https_match) {
- let m1 = m.get(1).cloned().unwrap_or_default();
- let m2 = m.get(2).cloned().unwrap_or_default();
+ Some(&mut m),
+ )
+ .unwrap_or(false)
+ || Preg::is_match_strict_groups3(
+ &format!(
+ "{{^https?://{}/(.*?)(?:\\.git)?$}}i",
+ Self::get_github_domains_regex(&*self.config.borrow())
+ ),
+ url,
+ Some(&mut m),
+ )
+ .unwrap_or(false);
+ if github_matched {
+ let m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default();
+ let m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default();
if !self.io.has_authentication(&m1) {
let mut git_hub_util = GitHub::new(
- self.io.as_ref(),
- &self.config,
- &self.process,
- self.http_downloader
- .as_ref()
- .unwrap_or(&HttpDownloader::default()),
- );
+ self.io.clone_box(),
+ std::rc::Rc::clone(&self.config),
+ Some(std::rc::Rc::clone(&self.process)),
+ self.http_downloader.clone(),
+ )?;
let message = "Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos";
if !git_hub_util.authorize_oauth(&m1) && self.io.is_interactive() {
@@ -396,7 +420,7 @@ impl Git {
);
if run_commands_inline(
&auth_url,
- &mut self.process,
+ &mut *self.process.borrow_mut(),
&mut last_command,
command_output.as_deref_mut(),
) == 0
@@ -405,27 +429,35 @@ impl Git {
}
credentials = vec![rawurlencode(&username), rawurlencode(&password)];
- error_msg = self.process.get_error_output().to_string();
+ error_msg = self.process.borrow().get_error_output().to_string();
}
- } else if let Some(m) = Preg::is_match_strict_groups(
- r"{^(https?)://(bitbucket\.org)/(.*?)(?:\.git)?$}i",
- url,
- )
- .or_else(|| {
- Preg::is_match_strict_groups(r"{^(git)@(bitbucket\.org):(.+?\.git)$}i", url)
- }) {
+ } else if {
+ let bb_matched = Preg::is_match_strict_groups3(
+ r"{^(https?)://(bitbucket\.org)/(.*?)(?:\.git)?$}i",
+ url,
+ Some(&mut m),
+ )
+ .unwrap_or(false)
+ || Preg::is_match_strict_groups3(
+ r"{^(git)@(bitbucket\.org):(.+?\.git)$}i",
+ url,
+ Some(&mut m),
+ )
+ .unwrap_or(false);
+ bb_matched
+ } {
// bitbucket either through oauth or app password, with fallback to ssh.
let mut bitbucket_util = Bitbucket::new(
- self.io.as_ref(),
- &self.config,
- &self.process,
- self.http_downloader
- .as_ref()
- .unwrap_or(&HttpDownloader::default()),
- );
+ self.io.clone_box(),
+ std::rc::Rc::clone(&self.config),
+ Some(std::rc::Rc::clone(&self.process)),
+ self.http_downloader.clone(),
+ None,
+ )?;
- let domain = m.get(2).cloned().unwrap_or_default();
- let mut repo_with_git_part = m.get(3).cloned().unwrap_or_default();
+ let domain = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default();
+ let mut repo_with_git_part =
+ m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default();
if !str_ends_with(&repo_with_git_part, ".git") {
repo_with_git_part.push_str(".git");
}
@@ -476,7 +508,7 @@ impl Git {
if run_commands_inline(
&auth_url,
- &mut self.process,
+ &mut *self.process.borrow_mut(),
&mut last_command,
command_output.as_deref_mut(),
) == 0
@@ -523,7 +555,7 @@ impl Git {
);
if run_commands_inline(
&auth_url,
- &mut self.process,
+ &mut *self.process.borrow_mut(),
&mut last_command,
command_output.as_deref_mut(),
) == 0
@@ -535,17 +567,14 @@ impl Git {
}
// Falling back to ssh
let ssh_url = format!("git@bitbucket.org:{}", repo_with_git_part);
- self.io.write_error(
- PhpMixed::String(
- " No bitbucket authentication configured. Falling back to ssh."
- .to_string(),
- ),
+ self.io.write_error3(
+ " No bitbucket authentication configured. Falling back to ssh.",
true,
io_interface::NORMAL,
);
if run_commands_inline(
&ssh_url,
- &mut self.process,
+ &mut *self.process.borrow_mut(),
&mut last_command,
command_output.as_deref_mut(),
) == 0
@@ -553,39 +582,42 @@ impl Git {
return Ok(());
}
- error_msg = self.process.get_error_output().to_string();
- } else if let Some(m) = Preg::is_match_strict_groups(
- &format!(
- "{{^(git)@{}:(.+?\\.git)$}}i",
- Self::get_gitlab_domains_regex(&self.config)
- ),
- url,
- )
- .or_else(|| {
- Preg::is_match_strict_groups(
+ error_msg = self.process.borrow().get_error_output().to_string();
+ } else if {
+ let gl_matched = Preg::is_match_strict_groups3(
&format!(
- "{{^(https?)://{}/(.*)}}i",
- Self::get_gitlab_domains_regex(&self.config)
+ "{{^(git)@{}:(.+?\\.git)$}}i",
+ Self::get_gitlab_domains_regex(&*self.config.borrow())
),
url,
+ Some(&mut m),
)
- }) {
- let mut m1 = m.get(1).cloned().unwrap_or_default();
- let m2 = m.get(2).cloned().unwrap_or_default();
- let m3 = m.get(3).cloned().unwrap_or_default();
+ .unwrap_or(false)
+ || Preg::is_match_strict_groups3(
+ &format!(
+ "{{^(https?)://{}/(.*)}}i",
+ Self::get_gitlab_domains_regex(&*self.config.borrow())
+ ),
+ url,
+ Some(&mut m),
+ )
+ .unwrap_or(false);
+ gl_matched
+ } {
+ let mut m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default();
+ let m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default();
+ let m3 = m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default();
if m1 == "git" {
m1 = "https".to_string();
}
if !self.io.has_authentication(&m2) {
let mut git_lab_util = GitLab::new(
- self.io.as_ref(),
- &self.config,
- &self.process,
- self.http_downloader
- .as_ref()
- .unwrap_or(&HttpDownloader::default()),
- );
+ self.io.clone_box(),
+ std::rc::Rc::clone(&self.config),
+ Some(std::rc::Rc::clone(&self.process)),
+ self.http_downloader.clone(),
+ )?;
let message =
"Cloning failed, enter your GitLab credentials to access private repos";
@@ -635,7 +667,7 @@ impl Git {
if run_commands_inline(
&auth_url,
- &mut self.process,
+ &mut *self.process.borrow_mut(),
&mut last_command,
command_output.as_deref_mut(),
) == 0
@@ -644,13 +676,13 @@ impl Git {
}
credentials = vec![rawurlencode(&username), rawurlencode(&password)];
- error_msg = self.process.get_error_output().to_string();
+ error_msg = self.process.borrow().get_error_output().to_string();
}
} else if let Some(m) = self.get_authentication_failure(url) {
// private non-github/gitlab/bitbucket repo that failed to authenticate
- let mut m1 = m.get(1).cloned().unwrap_or_default();
- let mut m2 = m.get(2).cloned().unwrap_or_default();
- let m3 = m.get(3).cloned().unwrap_or_default();
+ let mut m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default();
+ let mut m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default();
+ let m3 = m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default();
let mut auth_parts: Option<String> = None;
if str_contains(&m2, "@") {
let parts = explode("@", &m2);
@@ -674,16 +706,13 @@ impl Git {
}
}
- self.io.write_error(
- PhpMixed::String(format!(
- " Authentication required (<info>{}</info>):",
- m2
- )),
+ self.io.write_error3(
+ &format!(" Authentication required (<info>{}</info>):", m2),
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String(format!("<warning>{}</warning>", trim(&error_msg, None))),
+ self.io.write_error3(
+ &format!("<warning>{}</warning>", trim(&error_msg, None)),
true,
io_interface::VERBOSE,
);
@@ -706,7 +735,7 @@ impl Git {
self.io.ask_and_hide_answer(" Password: ".to_string()),
);
auth = Some(auth_map);
- store_auth = self.config.get("store-auths");
+ store_auth = self.config.borrow_mut().get("store-auths");
}
if let Some(auth_inner) = auth.as_ref() {
@@ -731,27 +760,28 @@ impl Git {
if run_commands_inline(
&auth_url,
- &mut self.process,
+ &mut *self.process.borrow_mut(),
&mut last_command,
command_output.as_deref_mut(),
) == 0
{
self.io
.set_authentication(m2.clone(), username, Some(password));
- let mut auth_helper = AuthHelper::new(self.io.as_ref(), &self.config);
+ let mut auth_helper =
+ AuthHelper::new(self.io.clone_box(), std::rc::Rc::clone(&self.config));
auth_helper.store_auth(&m2, &store_auth);
return Ok(());
}
credentials = vec![rawurlencode(&username), rawurlencode(&password)];
- error_msg = self.process.get_error_output().to_string();
+ error_msg = self.process.borrow().get_error_output().to_string();
}
}
if initial_clone {
if let Some(ref orig) = orig_cwd {
- self.filesystem.remove_directory(orig);
+ self.filesystem.borrow_mut().remove_directory(orig);
}
}
@@ -765,7 +795,7 @@ impl Git {
}
_ => last_command.as_string().unwrap_or("").to_string(),
};
- let mut error_msg = self.process.get_error_output().to_string();
+ let mut error_msg = self.process.borrow().get_error_output().to_string();
if (credentials.len() as i64) > 0 {
last_command_str = self.mask_credentials(&last_command_str, &credentials);
error_msg = self.mask_credentials(&error_msg, &credentials);
@@ -787,11 +817,11 @@ impl Git {
.unwrap_or(false)
&& composer_disable_network.as_deref() != Some("prime")
{
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"<warning>Aborting git mirror sync of {} as network is disabled</warning>",
url
- )),
+ ),
true,
io_interface::NORMAL,
);
@@ -802,7 +832,7 @@ impl Git {
// update the repo if it is a valid git repository
let mut output = String::new();
if is_dir(dir)
- && self.process.execute(
+ && self.process.borrow_mut().execute_args(
&vec![
"git".to_string(),
"rev-parse".to_string(),
@@ -855,8 +885,8 @@ impl Git {
);
if let Err(e) = try_result {
- self.io.write_error(
- PhpMixed::String(format!("<error>Sync mirror failed: {}</error>", e)),
+ self.io.write_error3(
+ &format!("<error>Sync mirror failed: {}</error>", e),
true,
io_interface::DEBUG,
);
@@ -866,10 +896,10 @@ impl Git {
return Ok(true);
}
- Self::check_for_repo_ownership_error(self.process.get_error_output(), dir, None)?;
+ Self::check_for_repo_ownership_error(self.process.borrow().get_error_output(), dir, None)?;
// clean up directory and do a fresh clone into it
- self.filesystem.remove_directory(dir);
+ self.filesystem.borrow_mut().remove_directory(dir);
self.run_commands(
vec![vec![
@@ -915,15 +945,12 @@ impl Git {
if Preg::is_match(r"{^[a-f0-9]{40}$}", r#ref).unwrap_or(false)
&& pretty_version.is_some()
{
- let branch = Preg::replace(
- r"{(?:^dev-|(?:\.x)?-dev$)}i",
- "",
- pretty_version.unwrap().to_string(),
- );
+ let branch =
+ Preg::replace(r"{(?:^dev-|(?:\.x)?-dev$)}i", "", &pretty_version.unwrap());
let mut branches: Option<String> = None;
let mut tags: Option<String> = None;
let mut output = String::new();
- if self.process.execute(
+ if self.process.borrow_mut().execute_args(
&vec!["git".to_string(), "branch".to_string()],
&mut output,
Some(dir.to_string()),
@@ -932,7 +959,7 @@ impl Git {
branches = Some(output);
}
let mut output = String::new();
- if self.process.execute(
+ if self.process.borrow_mut().execute_args(
&vec!["git".to_string(), "tag".to_string()],
&mut output,
Some(dir.to_string()),
@@ -972,7 +999,9 @@ impl Git {
Ok(false)
}
- pub fn get_no_show_signature_flag(process: &ProcessExecutor) -> String {
+ pub fn get_no_show_signature_flag(
+ process: &std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ ) -> String {
let git_version = Self::get_version(process);
if let Some(v) = git_version {
if version_compare(&v, "2.10.0-rc0", ">=") {
@@ -984,7 +1013,9 @@ impl Git {
}
/// @return list<string>
- pub fn get_no_show_signature_flags(process: &ProcessExecutor) -> Vec<String> {
+ pub fn get_no_show_signature_flags(
+ process: &std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ ) -> Vec<String> {
let flags = Self::get_no_show_signature_flag(process);
if flags.is_empty() {
return vec![];
@@ -996,7 +1027,9 @@ impl Git {
/// Checks if git version supports --no-commit-header flag (git 2.33+)
///
/// @internal
- pub fn supports_no_commit_header_flag(process: &ProcessExecutor) -> bool {
+ pub fn supports_no_commit_header_flag(
+ process: &std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ ) -> bool {
let git_version = Self::get_version(process);
git_version
@@ -1010,7 +1043,7 @@ impl Git {
/// @param list<string> $arguments Additional arguments for git rev-list
/// @return non-empty-list<string>
pub fn build_rev_list_command(
- process: &ProcessExecutor,
+ process: &std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
arguments: Vec<String>,
) -> Vec<String> {
let mut command = vec!["git".to_string(), "rev-list".to_string()];
@@ -1028,20 +1061,23 @@ impl Git {
/// "commit <hash>" before formatted output. This removes those lines.
///
/// @internal
- pub fn parse_rev_list_output(output: &str, process: &ProcessExecutor) -> String {
+ pub fn parse_rev_list_output(
+ output: &str,
+ process: &std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ ) -> String {
// If git supports --no-commit-header, output is already clean
if Self::supports_no_commit_header_flag(process) {
return output.to_string();
}
// Filter out "commit <hash>" lines for older git versions
- Preg::replace(r"{^commit [a-f0-9]{40}\n?}m", "", output.to_string())
+ Preg::replace(r"{^commit [a-f0-9]{40}\n?}m", "", output).unwrap_or_default()
}
fn check_ref_is_in_mirror(&mut self, dir: &str, r#ref: &str) -> Result<bool> {
let mut output = String::new();
if is_dir(dir)
- && self.process.execute(
+ && self.process.borrow_mut().execute_args(
&vec![
"git".to_string(),
"rev-parse".to_string(),
@@ -1053,7 +1089,7 @@ impl Git {
&& trim(&output, None) == "."
{
let mut ignored_output = String::new();
- let exit_code = self.process.execute(
+ let exit_code = self.process.borrow_mut().execute_args(
&vec![
"git".to_string(),
"rev-parse".to_string(),
@@ -1068,14 +1104,19 @@ impl Git {
return Ok(true);
}
}
- Self::check_for_repo_ownership_error(self.process.get_error_output(), dir, None)?;
+ Self::check_for_repo_ownership_error(self.process.borrow().get_error_output(), dir, None)?;
Ok(false)
}
/// @return array<int, string>|null
- fn get_authentication_failure(&self, url: &str) -> Option<IndexMap<i32, String>> {
- let m = Preg::is_match_strict_groups(r"{^(https?://)([^/]+)(.*)$}i", url)?;
+ fn get_authentication_failure(&self, url: &str) -> Option<IndexMap<CaptureKey, String>> {
+ let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
+ if !Preg::is_match_strict_groups3(r"{^(https?://)([^/]+)(.*)$}i", url, Some(&mut m))
+ .unwrap_or(false)
+ {
+ return None;
+ }
let auth_failures = [
"fatal: Authentication failed",
@@ -1085,7 +1126,7 @@ impl Git {
"fatal: could not read Username",
];
- let error_output = self.process.get_error_output();
+ let error_output = self.process.borrow().get_error_output();
for auth_failure in &auth_failures {
if strpos(error_output, auth_failure).is_some() {
return Some(m);
@@ -1112,7 +1153,7 @@ impl Git {
let mut output_mixed = PhpMixed::String(String::new());
if is_local_path_repository {
let mut output = String::new();
- self.process.execute(
+ self.process.borrow_mut().execute_args(
&vec![
"git".to_string(),
"remote".to_string(),
@@ -1154,12 +1195,23 @@ impl Git {
let lines = self
.process
+ .borrow()
.split_lines(output_mixed.as_string().unwrap_or(""));
for line in lines {
- if let Some(matches) =
- Preg::is_match_strict_groups(r"{^\s*HEAD branch:\s(.+)\s*$}m", &line)
+ let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::is_match_strict_groups3(
+ r"{^\s*HEAD branch:\s(.+)\s*$}m",
+ &line,
+ Some(&mut matches),
+ )
+ .unwrap_or(false)
{
- return Ok(Some(matches.get(1).cloned().unwrap_or_default()));
+ return Ok(Some(
+ matches
+ .get(&CaptureKey::ByIndex(1))
+ .cloned()
+ .unwrap_or_default(),
+ ));
}
}
@@ -1168,11 +1220,11 @@ impl Git {
match result {
Ok(v) => v,
Err(e) => {
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"<error>Failed to fetch root identifier from remote: {}</error>",
e
- )),
+ ),
true,
io_interface::DEBUG,
);
@@ -1181,7 +1233,7 @@ impl Git {
}
}
- pub fn clean_env(process: &ProcessExecutor) {
+ pub fn clean_env(process: &std::rc::Rc<std::cell::RefCell<ProcessExecutor>>) {
// PHP: $process ?? new ProcessExecutor()
let git_version = Self::get_version(process);
if let Some(v) = git_version {
@@ -1249,7 +1301,7 @@ impl Git {
clearstatcache();
let mut ignored_output = String::new();
- if self.process.execute(
+ if self.process.borrow_mut().execute_args(
&vec!["git".to_string(), "--version".to_string()],
&mut ignored_output,
None,
@@ -1259,7 +1311,7 @@ impl Git {
message: Url::sanitize(format!(
"Failed to clone {}, git was not found, check that it is installed and in your PATH env.\n\n{}",
url,
- self.process.get_error_output()
+ self.process.borrow().get_error_output()
)),
code: 0,
}
@@ -1276,7 +1328,9 @@ impl Git {
/// Retrieves the current git version.
///
/// @return string|null The git version number, if present.
- pub fn get_version(process: &ProcessExecutor) -> Option<String> {
+ pub fn get_version(
+ process: &std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ ) -> Option<String> {
let mut version = VERSION.lock().unwrap();
if version.is_none() {
*version = Some(None);
@@ -1285,10 +1339,15 @@ impl Git {
// For now, mimic the call signature (compilation fix is Phase B)
let exit_code: i64 = 0; // process.execute(&["git", "--version"].map(String::from).to_vec(), &mut output, None);
if exit_code == 0 {
- if let Some(matches) =
- Preg::is_match_strict_groups(r"/^git version (\d+(?:\.\d+)+)/m", &output)
+ let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::is_match_strict_groups3(
+ r"/^git version (\d+(?:\.\d+)+)/m",
+ &output,
+ Some(&mut matches),
+ )
+ .unwrap_or(false)
{
- *version = Some(matches.get(1).cloned());
+ *version = Some(matches.get(&CaptureKey::ByIndex(1)).cloned());
}
}
}
diff --git a/crates/shirabe/src/util/github.rs b/crates/shirabe/src/util/github.rs
index 5fa88a6..cfb3d0c 100644
--- a/crates/shirabe/src/util/github.rs
+++ b/crates/shirabe/src/util/github.rs
@@ -2,7 +2,7 @@
use crate::io::io_interface;
use indexmap::IndexMap;
-use shirabe_external_packages::composer::pcre::preg::Preg;
+use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg};
use shirabe_php_shim::{PhpMixed, date, stripos, strtolower};
use crate::config::Config;
@@ -15,9 +15,9 @@ use crate::util::process_executor::ProcessExecutor;
#[derive(Debug)]
pub struct GitHub {
io: Box<dyn IOInterface>,
- config: Config,
- process: ProcessExecutor,
- http_downloader: HttpDownloader,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
+ process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>,
}
impl GitHub {
@@ -26,14 +26,20 @@ impl GitHub {
pub fn new(
io: Box<dyn IOInterface>,
- config: Config,
- process: Option<ProcessExecutor>,
- http_downloader: Option<HttpDownloader>,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
+ process: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>,
+ http_downloader: Option<std::rc::Rc<std::cell::RefCell<HttpDownloader>>>,
) -> anyhow::Result<Self> {
- let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io));
+ let process = process.unwrap_or_else(|| {
+ std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(&*io)))
+ });
let http_downloader = match http_downloader {
Some(h) => h,
- None => Factory::create_http_downloader(&*io, &config, IndexMap::new())?,
+ None => std::rc::Rc::new(std::cell::RefCell::new(Factory::create_http_downloader(
+ &*io,
+ &config,
+ IndexMap::new(),
+ )?)),
};
Ok(Self {
io,
@@ -44,7 +50,7 @@ impl GitHub {
}
pub fn authorize_oauth(&mut self, origin_url: &str) -> bool {
- let github_domains = self.config.get("github-domains");
+ let github_domains = self.config.borrow_mut().get("github-domains");
let domains = match github_domains.as_array() {
Some(arr) => arr.clone(),
None => return false,
@@ -55,7 +61,7 @@ impl GitHub {
}
let mut output = String::new();
- if self.process.execute(
+ if self.process.borrow_mut().execute_args(
&[
"git".to_string(),
"config".to_string(),
@@ -82,16 +88,13 @@ impl GitHub {
message: Option<&str>,
) -> anyhow::Result<bool> {
if let Some(msg) = message {
- self.io.write_error(
- PhpMixed::String(msg.to_string()),
- true,
- io_interface::NORMAL,
- );
+ self.io.write_error3(msg, true, io_interface::NORMAL);
}
let mut note = "Composer".to_string();
let expose_hostname = self
.config
+ .borrow_mut()
.get("github-expose-hostname")
.as_bool()
.unwrap_or(false);
@@ -99,7 +102,8 @@ impl GitHub {
let mut output = String::new();
if self
.process
- .execute(&["hostname".to_string()], &mut output, None)
+ .borrow_mut()
+ .execute_args(&["hostname".to_string()], &mut output, None)
== 0
{
note += &format!(" on {}", output.trim());
@@ -107,105 +111,87 @@ impl GitHub {
}
note += &format!(" {}", date("Y-m-d Hi", None));
- let local_auth_config = self.config.get_local_auth_config_source();
+ let local_auth_config = self.config.borrow().get_local_auth_config_source();
- self.io.write_error(
- PhpMixed::List(vec![
- Box::new(PhpMixed::String(
- "You need to provide a GitHub access token.".to_string(),
- )),
- Box::new(PhpMixed::String(format!(
- "Tokens will be stored in plain text in \"{}\" for future use by Composer.",
- local_auth_config
- .as_ref()
- .map(|c| format!("{} OR ", c.get_name()))
- .unwrap_or_default()
- + &self.config.get_auth_config_source().get_name()
- ))),
- Box::new(PhpMixed::String(
- "Due to the security risk of tokens being exfiltrated, use tokens with short expiration times and only the minimum permissions necessary.".to_string(),
- )),
- Box::new(PhpMixed::String(String::new())),
- Box::new(PhpMixed::String(
- "Carefully consider the following options in order:".to_string(),
- )),
- Box::new(PhpMixed::String(String::new())),
- ]),
- true,
- io_interface::NORMAL,
- );
+ self.io.write_error3(PhpMixed::List(vec![
+ Box::new(PhpMixed::String(
+ "You need to provide a GitHub access token.".to_string(),
+ )),
+ Box::new(PhpMixed::String(format!(
+ "Tokens will be stored in plain text in \"{}\" for future use by Composer.",
+ local_auth_config
+ .as_ref()
+ .map(|c| format!("{} OR ", c.get_name()))
+ .unwrap_or_default()
+ + &self.config.borrow().get_auth_config_source().get_name()
+ ))),
+ Box::new(PhpMixed::String(
+ "Due to the security risk of tokens being exfiltrated, use tokens with short expiration times and only the minimum permissions necessary.".to_string(),
+ )),
+ Box::new(PhpMixed::String(String::new())),
+ Box::new(PhpMixed::String(
+ "Carefully consider the following options in order:".to_string(),
+ )),
+ Box::new(PhpMixed::String(String::new())),
+ ]), true, io_interface::NORMAL);
let encoded_note = shirabe_php_shim::rawurlencode(&note).replace("%20", "+");
- self.io.write_error(
- PhpMixed::List(vec![
- Box::new(PhpMixed::String(
- "1. When you don't use 'vcs' type 'repositories' in composer.json and do not need to clone source or download dist files".to_string(),
- )),
- Box::new(PhpMixed::String(
- "from private GitHub repositories over HTTPS, use a fine-grained token with read-only access to public information.".to_string(),
- )),
- Box::new(PhpMixed::String(
- "Use the following URL to create such a token:".to_string(),
- )),
- Box::new(PhpMixed::String(format!(
- "https://{}/settings/personal-access-tokens/new?name={}",
- origin_url, encoded_note
- ))),
- Box::new(PhpMixed::String(String::new())),
- ]),
- true,
- io_interface::NORMAL,
- );
+ self.io.write_error3(PhpMixed::List(vec![
+ Box::new(PhpMixed::String(
+ "1. When you don't use 'vcs' type 'repositories' in composer.json and do not need to clone source or download dist files".to_string(),
+ )),
+ Box::new(PhpMixed::String(
+ "from private GitHub repositories over HTTPS, use a fine-grained token with read-only access to public information.".to_string(),
+ )),
+ Box::new(PhpMixed::String(
+ "Use the following URL to create such a token:".to_string(),
+ )),
+ Box::new(PhpMixed::String(format!(
+ "https://{}/settings/personal-access-tokens/new?name={}",
+ origin_url, encoded_note
+ ))),
+ Box::new(PhpMixed::String(String::new())),
+ ]), true, io_interface::NORMAL);
- self.io.write_error(
- PhpMixed::List(vec![
- Box::new(PhpMixed::String(
- "2. When all relevant _private_ GitHub repositories belong to a single user or organisation, use a fine-grained token with".to_string(),
- )),
- Box::new(PhpMixed::String(
- "repository \"content\" read-only permissions. You can start with the following URL, but you may need to change the resource owner".to_string(),
- )),
- Box::new(PhpMixed::String(
- "to the right user or organisation. Additionally, you can scope permissions down to apply only to selected repositories.".to_string(),
- )),
- Box::new(PhpMixed::String(format!(
- "https://{}/settings/personal-access-tokens/new?contents=read&name={}",
- origin_url, encoded_note
- ))),
- Box::new(PhpMixed::String(String::new())),
- ]),
- true,
- io_interface::NORMAL,
- );
+ self.io.write_error3(PhpMixed::List(vec![
+ Box::new(PhpMixed::String(
+ "2. When all relevant _private_ GitHub repositories belong to a single user or organisation, use a fine-grained token with".to_string(),
+ )),
+ Box::new(PhpMixed::String(
+ "repository \"content\" read-only permissions. You can start with the following URL, but you may need to change the resource owner".to_string(),
+ )),
+ Box::new(PhpMixed::String(
+ "to the right user or organisation. Additionally, you can scope permissions down to apply only to selected repositories.".to_string(),
+ )),
+ Box::new(PhpMixed::String(format!(
+ "https://{}/settings/personal-access-tokens/new?contents=read&name={}",
+ origin_url, encoded_note
+ ))),
+ Box::new(PhpMixed::String(String::new())),
+ ]), true, io_interface::NORMAL);
- self.io.write_error(
- PhpMixed::List(vec![
- Box::new(PhpMixed::String(
- "3. A \"classic\" token grants broad permissions on your behalf to all repositories accessible by you.".to_string(),
- )),
- Box::new(PhpMixed::String(
- "This may include write permissions, even though not needed by Composer. Use it only when you need to access".to_string(),
- )),
- Box::new(PhpMixed::String(
- "private repositories across multiple organisations at the same time and using directory-specific authentication sources".to_string(),
- )),
- Box::new(PhpMixed::String(
- "is not an option. You can generate a classic token here:".to_string(),
- )),
- Box::new(PhpMixed::String(format!(
- "https://{}/settings/tokens/new?scopes=repo&description={}",
- origin_url, encoded_note
- ))),
- Box::new(PhpMixed::String(String::new())),
- ]),
- true,
- io_interface::NORMAL,
- );
+ self.io.write_error3(PhpMixed::List(vec![
+ Box::new(PhpMixed::String(
+ "3. A \"classic\" token grants broad permissions on your behalf to all repositories accessible by you.".to_string(),
+ )),
+ Box::new(PhpMixed::String(
+ "This may include write permissions, even though not needed by Composer. Use it only when you need to access".to_string(),
+ )),
+ Box::new(PhpMixed::String(
+ "private repositories across multiple organisations at the same time and using directory-specific authentication sources".to_string(),
+ )),
+ Box::new(PhpMixed::String(
+ "is not an option. You can generate a classic token here:".to_string(),
+ )),
+ Box::new(PhpMixed::String(format!(
+ "https://{}/settings/tokens/new?scopes=repo&description={}",
+ origin_url, encoded_note
+ ))),
+ Box::new(PhpMixed::String(String::new())),
+ ]), true, io_interface::NORMAL);
- self.io.write_error(
- PhpMixed::String(
- "For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth".to_string(),
- ),
+ self.io.write_error3(
+ "For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth",
true,
io_interface::NORMAL,
);
@@ -227,15 +213,13 @@ impl GitHub {
.to_string();
if token.is_empty() {
- self.io.write_error(
- PhpMixed::String("<warning>No token given, aborting.</warning>".to_string()),
+ self.io.write_error3(
+ "<warning>No token given, aborting.</warning>",
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String(
- "You can also add it manually later by using \"composer config --global --auth github-oauth.github.com <token>\"".to_string(),
- ),
+ self.io.write_error3(
+ "You can also add it manually later by using \"composer config --global --auth github-oauth.github.com <token>\"",
true,
io_interface::NORMAL,
);
@@ -263,20 +247,19 @@ impl GitHub {
match self
.http_downloader
+ .borrow_mut()
.get(&format!("https://{}", api_url), &http_options)
{
Ok(_) => {}
Err(te) => {
if te.code == 403 || te.code == 401 {
- self.io.write_error(
- PhpMixed::String("<error>Invalid token provided.</error>".to_string()),
+ self.io.write_error3(
+ "<error>Invalid token provided.</error>",
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String(
- "You can also add it manually later by using \"composer config --global --auth github-oauth.github.com <token>\"".to_string(),
- ),
+ self.io.write_error3(
+ "You can also add it manually later by using \"composer config --global --auth github-oauth.github.com <token>\"",
true,
io_interface::NORMAL,
);
@@ -286,12 +269,18 @@ impl GitHub {
}
}
- let use_local =
- store_in_local_auth_config && self.config.get_local_auth_config_source().is_some();
+ let use_local = store_in_local_auth_config
+ && self
+ .config
+ .borrow()
+ .get_local_auth_config_source()
+ .is_some();
let auth_config_source_name;
if use_local {
- let mut auth_config_source = self.config.get_local_auth_config_source().unwrap();
+ let mut auth_config_source =
+ self.config.borrow().get_local_auth_config_source().unwrap();
self.config
+ .borrow()
.get_config_source()
.remove_config_setting(&format!("github-oauth.{}", origin_url))?;
auth_config_source.add_config_setting(
@@ -299,8 +288,9 @@ impl GitHub {
PhpMixed::String(token),
)?;
} else {
- let mut auth_config_source = self.config.get_auth_config_source();
+ let mut auth_config_source = self.config.borrow().get_auth_config_source();
self.config
+ .borrow()
.get_config_source()
.remove_config_setting(&format!("github-oauth.{}", origin_url))?;
auth_config_source.add_config_setting(
@@ -309,8 +299,8 @@ impl GitHub {
)?;
}
- self.io.write_error(
- PhpMixed::String("<info>Token stored successfully.</info>".to_string()),
+ self.io.write_error3(
+ "<info>Token stored successfully.</info>",
true,
io_interface::NORMAL,
);
@@ -358,8 +348,11 @@ impl GitHub {
if stripos(header, "x-github-sso: required").is_none() {
continue;
}
- if let Some(caps) = Preg::match_strict_groups(r"{\burl=(?P<url>[^\s;]+)}", header) {
- return caps.get("url").cloned();
+ let mut caps: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::match_strict_groups3(r"{\burl=(?P<url>[^\s;]+)}", header, Some(&mut caps))
+ .unwrap_or(false)
+ {
+ return caps.get(&CaptureKey::ByName("url".to_string())).cloned();
}
}
diff --git a/crates/shirabe/src/util/gitlab.rs b/crates/shirabe/src/util/gitlab.rs
index 191e69d..11b7a21 100644
--- a/crates/shirabe/src/util/gitlab.rs
+++ b/crates/shirabe/src/util/gitlab.rs
@@ -15,22 +15,28 @@ use crate::util::process_executor::ProcessExecutor;
#[derive(Debug)]
pub struct GitLab {
pub(crate) io: Box<dyn IOInterface>,
- pub(crate) config: Config,
- pub(crate) process: ProcessExecutor,
- pub(crate) http_downloader: HttpDownloader,
+ pub(crate) config: std::rc::Rc<std::cell::RefCell<Config>>,
+ pub(crate) process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ pub(crate) http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>,
}
impl GitLab {
pub fn new(
io: Box<dyn IOInterface>,
- config: Config,
- process: Option<ProcessExecutor>,
- http_downloader: Option<HttpDownloader>,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
+ process: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>,
+ http_downloader: Option<std::rc::Rc<std::cell::RefCell<HttpDownloader>>>,
) -> anyhow::Result<Self> {
- let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io));
+ let process = process.unwrap_or_else(|| {
+ std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(&*io)))
+ });
let http_downloader = match http_downloader {
Some(h) => h,
- None => Factory::create_http_downloader(&*io, &config, IndexMap::new())?,
+ None => std::rc::Rc::new(std::cell::RefCell::new(Factory::create_http_downloader(
+ &*io,
+ &config,
+ IndexMap::new(),
+ )?)),
};
Ok(Self {
io,
@@ -45,7 +51,7 @@ impl GitLab {
let bc_origin_url =
Preg::replace("{:\\d+}", "", origin_url).unwrap_or_else(|_| origin_url.to_string());
- let gitlab_domains = self.config.get("gitlab-domains");
+ let gitlab_domains = self.config.borrow_mut().get("gitlab-domains");
let domains = match gitlab_domains.as_array() {
Some(arr) => arr.clone(),
None => return false,
@@ -60,7 +66,7 @@ impl GitLab {
// if available use token from git config
let mut output = String::new();
- if self.process.execute(
+ if self.process.borrow_mut().execute_args(
&[
"git".to_string(),
"config".to_string(),
@@ -81,7 +87,7 @@ impl GitLab {
// if available use deploy token from git config
let mut token_user = String::new();
let mut token_password = String::new();
- if self.process.execute(
+ if self.process.borrow_mut().execute_args(
&[
"git".to_string(),
"config".to_string(),
@@ -90,7 +96,7 @@ impl GitLab {
&mut token_user,
None,
) == 0
- && self.process.execute(
+ && self.process.borrow_mut().execute_args(
&[
"git".to_string(),
"config".to_string(),
@@ -109,7 +115,7 @@ impl GitLab {
}
// if available use token from composer config
- let auth_tokens = self.config.get("gitlab-token");
+ let auth_tokens = self.config.borrow_mut().get("gitlab-token");
let mut token: Option<PhpMixed> = None;
@@ -168,68 +174,53 @@ impl GitLab {
message: Option<&str>,
) -> anyhow::Result<bool> {
if let Some(msg) = message {
- self.io.write_error(
- PhpMixed::String(msg.to_string()),
- true,
- io_interface::NORMAL,
- );
+ self.io.write_error3(msg, true, io_interface::NORMAL);
}
- let local_auth_config = self.config.get_local_auth_config_source();
+ let local_auth_config = self.config.borrow().get_local_auth_config_source();
let personal_access_token_link = format!(
"{}://{}/-/user_settings/personal_access_tokens",
scheme, origin_url
);
let revoke_link = format!("{}://{}/-/user_settings/applications", scheme, origin_url);
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"A token will be created and stored in \"{}\", your password will never be stored",
local_auth_config
.as_ref()
.map(|c| format!("{} OR ", c.get_name()))
.unwrap_or_default()
- + &self.config.get_auth_config_source().get_name()
- )),
- true,
- io_interface::NORMAL,
- );
- self.io.write_error(
- PhpMixed::String("To revoke access to this token you can visit:".to_string()),
- true,
- io_interface::NORMAL,
- );
- self.io.write_error(
- PhpMixed::String(revoke_link.clone()),
- true,
- io_interface::NORMAL,
- );
- self.io.write_error(
- PhpMixed::String(
- "Alternatively you can setup an personal access token on:".to_string(),
+ + &self.config.borrow().get_auth_config_source().get_name()
),
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String(personal_access_token_link.clone()),
+ self.io.write_error3(
+ "To revoke access to this token you can visit:",
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String("and store it under \"gitlab-token\" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.".to_string()),
+ self.io
+ .write_error3(&revoke_link, true, io_interface::NORMAL);
+ self.io.write_error3(
+ "Alternatively you can setup an personal access token on:",
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String("https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token".to_string()),
+ self.io
+ .write_error3(&personal_access_token_link, true, io_interface::NORMAL);
+ self.io.write_error3(
+ "and store it under \"gitlab-token\" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.",
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String("for more details.".to_string()),
+ self.io.write_error3(
+ "https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token",
true,
io_interface::NORMAL,
);
+ self.io
+ .write_error3("for more details.", true, io_interface::NORMAL);
let mut store_in_local_auth_config = false;
if local_auth_config.is_some() {
@@ -261,41 +252,41 @@ impl GitLab {
.and_then(|v| v.as_string())
== Some("invalid_grant");
if is_invalid_grant {
- self.io.write_error(
- PhpMixed::String("Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token".to_string()),
+ self.io.write_error3(
+ "Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token",
true,
io_interface::NORMAL,
);
} else {
- self.io.write_error(
- PhpMixed::String("Bad credentials.".to_string()),
+ self.io.write_error3(
+ "Bad credentials.",
true,
io_interface::NORMAL,
);
}
} else {
- self.io.write_error(
- PhpMixed::String("Maximum number of login attempts exceeded. Please try again later.".to_string()),
+ self.io.write_error3(
+ "Maximum number of login attempts exceeded. Please try again later.",
true,
io_interface::NORMAL,
);
}
- self.io.write_error(
- PhpMixed::String("You can also manually create a personal access token enabling the \"read_api\" scope at:".to_string()),
+ self.io.write_error3(
+ "You can also manually create a personal access token enabling the \"read_api\" scope at:",
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String(personal_access_token_link.clone()),
+ self.io.write_error3(
+ &personal_access_token_link,
true,
io_interface::NORMAL,
);
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"Add it using \"composer config --global --auth gitlab-token.{} <token>\"",
origin_url
- )),
+ ),
true,
io_interface::NORMAL,
);
@@ -342,7 +333,7 @@ impl GitLab {
)?;
}
} else {
- let mut auth_config_source = self.config.get_auth_config_source();
+ let mut auth_config_source = self.config.borrow().get_auth_config_source();
if has_expires_in {
auth_config_source.add_config_setting(
&format!("gitlab-oauth.{}", origin_url),
@@ -375,8 +366,8 @@ impl GitLab {
Ok(r) => r,
Err(e) => match e.downcast::<TransportException>() {
Ok(te) => {
- self.io.write_error(
- PhpMixed::String(format!("Couldn't refresh access token: {}", te.message)),
+ self.io.write_error3(
+ &format!("Couldn't refresh access token: {}", te.message),
true,
io_interface::NORMAL,
);
@@ -400,10 +391,13 @@ impl GitLab {
);
// store value in user config in auth file
- self.config.get_auth_config_source().add_config_setting(
- &format!("gitlab-oauth.{}", origin_url),
- Self::build_oauth_config(&response, &access_token),
- )?;
+ self.config
+ .borrow()
+ .get_auth_config_source()
+ .add_config_setting(
+ &format!("gitlab-oauth.{}", origin_url),
+ Self::build_oauth_config(&response, &access_token),
+ )?;
Ok(true)
}
@@ -454,23 +448,21 @@ impl GitLab {
let token = self
.http_downloader
+ .borrow_mut()
.get(
&format!("{}://{}/oauth/token", scheme, api_url),
&PhpMixed::Array(options),
)?
.decode_json()?;
- self.io.write_error(
- PhpMixed::String("Token successfully created".to_string()),
- true,
- io_interface::NORMAL,
- );
+ self.io
+ .write_error3("Token successfully created", true, io_interface::NORMAL);
Ok(token)
}
pub fn is_oauth_expired(&self, origin_url: &str) -> bool {
- let auth_tokens = self.config.get("gitlab-oauth");
+ let auth_tokens = self.config.borrow_mut().get("gitlab-oauth");
if let Some(map) = auth_tokens.as_array() {
if let Some(token_info) = map.get(origin_url) {
if let Some(token_map) = token_info.as_array() {
@@ -489,7 +481,7 @@ impl GitLab {
}
fn refresh_token(&mut self, scheme: &str, origin_url: &str) -> anyhow::Result<PhpMixed> {
- let auth_tokens = self.config.get("gitlab-oauth");
+ let auth_tokens = self.config.borrow_mut().get("gitlab-oauth");
let refresh_token = auth_tokens
.as_array()
.and_then(|map| map.get(origin_url))
@@ -543,22 +535,23 @@ impl GitLab {
let token = self
.http_downloader
+ .borrow_mut()
.get(
&format!("{}://{}/oauth/token", scheme, origin_url),
&PhpMixed::Array(options),
)?
.decode_json()?;
- self.io.write_error(
- PhpMixed::String("GitLab token successfully refreshed".to_string()),
+ self.io.write_error3(
+ "GitLab token successfully refreshed",
true,
io_interface::VERY_VERBOSE,
);
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"To revoke access to this token you can visit {}://{}/-/user_settings/applications",
scheme, origin_url
- )),
+ ),
true,
io_interface::VERY_VERBOSE,
);
diff --git a/crates/shirabe/src/util/hg.rs b/crates/shirabe/src/util/hg.rs
index c3f4b6e..fa25a78 100644
--- a/crates/shirabe/src/util/hg.rs
+++ b/crates/shirabe/src/util/hg.rs
@@ -14,12 +14,16 @@ static VERSION: OnceLock<Option<String>> = OnceLock::new();
#[derive(Debug)]
pub struct Hg {
io: Box<dyn IOInterface>,
- config: Config,
- process: ProcessExecutor,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
+ process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
}
impl Hg {
- pub fn new(io: &dyn IOInterface, config: &Config, process: &ProcessExecutor) -> Self {
+ pub fn new(
+ io: &dyn IOInterface,
+ config: &Config,
+ process: &std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ ) -> Self {
todo!()
}
@@ -29,14 +33,19 @@ impl Hg {
url: String,
cwd: Option<String>,
) -> Result<()> {
- self.config.prohibit_url_by_config(&url, &*self.io)?;
+ self.config.borrow_mut().prohibit_url_by_config(
+ &url,
+ Some(&*self.io),
+ &indexmap::IndexMap::new(),
+ )?;
// Try as is
let command = command_callable(url.clone());
let mut ignored_output = String::new();
if self
.process
- .execute(&command, &mut ignored_output, cwd.clone())
+ .borrow_mut()
+ .execute_args(&command, &mut ignored_output, cwd.clone())
== 0
{
return Ok(());
@@ -82,11 +91,16 @@ impl Hg {
let command = command_callable(authenticated_url);
let mut ignored_output = String::new();
- if self.process.execute(&command, &mut ignored_output, cwd) == 0 {
+ if self
+ .process
+ .borrow_mut()
+ .execute_args(&command, &mut ignored_output, cwd)
+ == 0
+ {
return Ok(());
}
- let error = self.process.get_error_output();
+ let error = self.process.borrow().get_error_output();
return self
.throw_exception(&format!("Failed to clone {}, \n\n{}", url, error), &url);
}
@@ -106,7 +120,7 @@ impl Hg {
Url::sanitize(&format!(
"Failed to clone {}, hg was not found, check that it is installed and in your PATH env.\n\n{}",
url,
- self.process.get_error_output()
+ self.process.borrow().get_error_output()
))
);
}
@@ -114,11 +128,13 @@ impl Hg {
anyhow::bail!("{}", Url::sanitize(message));
}
- pub fn get_version(process: &ProcessExecutor) -> Option<&'static str> {
+ pub fn get_version(
+ process: &std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
+ ) -> Option<&'static str> {
VERSION
.get_or_init(|| {
let mut output = String::new();
- if process.execute(
+ if process.borrow_mut().execute_args(
&["hg".to_string(), "--version".to_string()],
&mut output,
None,
diff --git a/crates/shirabe/src/util/http/curl_downloader.rs b/crates/shirabe/src/util/http/curl_downloader.rs
index d335b8b..c475ad3 100644
--- a/crates/shirabe/src/util/http/curl_downloader.rs
+++ b/crates/shirabe/src/util/http/curl_downloader.rs
@@ -51,7 +51,7 @@ pub struct CurlDownloader {
/// @var IOInterface
io: Box<dyn IOInterface>,
/// @var Config
- config: Config,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
/// @var AuthHelper
auth_helper: AuthHelper,
/// @var float
@@ -124,7 +124,7 @@ impl CurlDownloader {
/// @param mixed[] $options
pub fn new(
io: Box<dyn IOInterface>,
- config: Config,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
_options: IndexMap<String, PhpMixed>,
_disable_tls: bool,
) -> Self {
@@ -336,10 +336,11 @@ impl CurlDownloader {
let original_options = options.clone();
// check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
- if !Preg::is_match(r"{^http://(repo\.)?packagist\.org/p/}", url)
+ if !Preg::is_match(r"{^http://(repo\.)?packagist\.org/p/}", url).unwrap_or(false)
|| (strpos(url, "$").is_none() && strpos(url, "%24").is_none())
{
self.config
+ .borrow_mut()
.prohibit_url_by_config(url, Some(&*self.io), &options)?;
}
@@ -681,13 +682,13 @@ impl CurlDownloader {
if attributes.get("redirects").and_then(|v| v.as_int()) == Some(0)
&& attributes.get("retries").and_then(|v| v.as_int()) == Some(0)
{
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"Downloading {}{}{}",
Url::sanitize(url.to_string()),
using_proxy,
if_modified
- )),
+ ),
true,
crate::io::io_interface::DEBUG,
);
@@ -830,11 +831,8 @@ impl CurlDownloader {
&& !TIMEOUT_WARNING.load(Ordering::Relaxed)
{
TIMEOUT_WARNING.store(true, Ordering::Relaxed);
- self.io.write_error(
- PhpMixed::String(
- "<warning>A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.</warning>"
- .to_string(),
- ),
+ self.io.write_error3(
+ "<warning>A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.</warning>",
true,
crate::io::io_interface::NORMAL,
);
@@ -904,8 +902,8 @@ impl CurlDownloader {
// CURLE_COULDNT_CONNECT, retry forcing IPv4 if no IP stack was selected
attributes.insert("ipResolve".to_string(), PhpMixed::Int(4));
}
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"Retrying ({}) {} due to curl error {}",
job.get("attributes")
.and_then(|v| v.as_array())
@@ -920,7 +918,7 @@ impl CurlDownloader {
.to_string()
),
errno
- )),
+ ),
true,
crate::io::io_interface::DEBUG,
);
@@ -936,8 +934,8 @@ impl CurlDownloader {
if errno == 55
/* CURLE_SEND_ERROR */
{
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"Retrying ({}) {} due to curl error {}",
job.get("attributes")
.and_then(|v| v.as_array())
@@ -952,7 +950,7 @@ impl CurlDownloader {
.to_string()
),
errno
- )),
+ ),
true,
crate::io::io_interface::DEBUG,
);
@@ -1073,8 +1071,8 @@ impl CurlDownloader {
.map(|(k, v)| (k.clone(), (**v).clone()))
.collect(),
));
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"[{}] {}",
status_code.unwrap_or(0),
Url::sanitize(
@@ -1083,7 +1081,7 @@ impl CurlDownloader {
.unwrap_or("")
.to_string()
)
- )),
+ ),
true,
crate::io::io_interface::DEBUG,
);
@@ -1149,8 +1147,8 @@ impl CurlDownloader {
.map(|(k, v)| (k.clone(), (**v).clone()))
.collect(),
));
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"[{}] {}",
status_code.unwrap_or(0),
Url::sanitize(
@@ -1159,7 +1157,7 @@ impl CurlDownloader {
.unwrap_or("")
.to_string()
)
- )),
+ ),
true,
crate::io::io_interface::DEBUG,
);
@@ -1292,8 +1290,8 @@ impl CurlDownloader {
.unwrap_or(0)
< self.max_retries
{
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"Retrying ({}) {} due to status code {}",
job.get("attributes")
.and_then(|v| v.as_array())
@@ -1308,7 +1306,7 @@ impl CurlDownloader {
.to_string()
),
sc
- )),
+ ),
true,
crate::io::io_interface::DEBUG,
);
@@ -1598,8 +1596,8 @@ impl CurlDownloader {
}
if !target_url.is_empty() {
- self.io.write_error(
- PhpMixed::String(sprintf(
+ self.io.write_error3(
+ &sprintf(
"Following redirect (%u) %s",
&[
PhpMixed::Int(
@@ -1612,7 +1610,7 @@ impl CurlDownloader {
),
PhpMixed::String(Url::sanitize(target_url.clone())),
],
- )),
+ ),
true,
crate::io::io_interface::DEBUG,
);
@@ -1697,7 +1695,7 @@ impl CurlDownloader {
}
// check for gitlab 404 when downloading archives
- let gitlab_domains = self.config.get("gitlab-domains");
+ let gitlab_domains = self.config.borrow_mut().get("gitlab-domains");
let gitlab_domains_list: Vec<Box<PhpMixed>> = match gitlab_domains {
PhpMixed::List(l) => l,
_ => Vec::new(),
@@ -1788,7 +1786,7 @@ impl CurlDownloader {
PhpMixed::Array(a) => a.into_iter().map(|(k, v)| (k, *v)).collect(),
_ => IndexMap::new(),
};
- let origin = Url::get_origin(&self.config, url);
+ let origin = Url::get_origin(&*self.config.borrow(), url);
let copy_to = job
.get("filename")
diff --git a/crates/shirabe/src/util/http/proxy_manager.rs b/crates/shirabe/src/util/http/proxy_manager.rs
index 5ed8269..2576dcb 100644
--- a/crates/shirabe/src/util/http/proxy_manager.rs
+++ b/crates/shirabe/src/util/http/proxy_manager.rs
@@ -50,10 +50,10 @@ impl ProxyManager {
request_url: &str,
) -> Result<RequestProxy, TransportException> {
if let Some(ref error) = self.error {
- return Err(TransportException::new(format!(
- "Unable to use a proxy: {}",
- error
- )));
+ return Err(TransportException::new(
+ format!("Unable to use a proxy: {}", error),
+ 0,
+ ));
}
let scheme = request_url.split("://").next().unwrap_or("").to_string();
@@ -85,19 +85,19 @@ impl ProxyManager {
// PHP_SAPI is always 'cli' for this application
let (env, name) = Self::get_proxy_env("http_proxy");
if let Some(env) = env {
- self.http_proxy = Some(ProxyItem::new(env, name));
+ self.http_proxy = Some(ProxyItem::new(env, name)?);
}
if self.http_proxy.is_none() {
let (env, name) = Self::get_proxy_env("cgi_http_proxy");
if let Some(env) = env {
- self.http_proxy = Some(ProxyItem::new(env, name));
+ self.http_proxy = Some(ProxyItem::new(env, name)?);
}
}
let (env, name) = Self::get_proxy_env("https_proxy");
if let Some(env) = env {
- self.https_proxy = Some(ProxyItem::new(env, name));
+ self.https_proxy = Some(ProxyItem::new(env, name)?);
}
let (env, _name) = Self::get_proxy_env("no_proxy");
@@ -122,7 +122,7 @@ impl ProxyManager {
fn no_proxy(&self, request_url: &str) -> bool {
match &self.no_proxy_handler {
None => false,
- Some(handler) => handler.test(request_url),
+ Some(handler) => handler.test(request_url).unwrap_or(false),
}
}
}
diff --git a/crates/shirabe/src/util/http/response.rs b/crates/shirabe/src/util/http/response.rs
index cf238c7..62458d6 100644
--- a/crates/shirabe/src/util/http/response.rs
+++ b/crates/shirabe/src/util/http/response.rs
@@ -41,7 +41,7 @@ impl Response {
pub fn get_status_message(&self) -> Option<String> {
let mut value = None;
for header in &self.headers {
- if Preg::is_match(r"(?i)^HTTP/\S+ \d+", header) {
+ if Preg::is_match(r"(?i)^HTTP/\S+ \d+", header).unwrap_or(false) {
// In case of redirects, headers contain the headers of all responses
// so we can not return directly and need to keep iterating
value = Some(header.clone());
diff --git a/crates/shirabe/src/util/http_downloader.rs b/crates/shirabe/src/util/http_downloader.rs
index 97b1f6e..71385ce 100644
--- a/crates/shirabe/src/util/http_downloader.rs
+++ b/crates/shirabe/src/util/http_downloader.rs
@@ -4,7 +4,7 @@ use anyhow::Result;
use indexmap::IndexMap;
use crate::util::silencer::Silencer;
-use shirabe_external_packages::composer::pcre::preg::Preg;
+use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg};
use shirabe_external_packages::react::promise::promise::Promise;
use shirabe_external_packages::react::promise::promise_interface::PromiseInterface;
use shirabe_php_shim::{
@@ -34,7 +34,7 @@ pub struct HttpDownloader {
/// @var IOInterface
io: Box<dyn IOInterface>,
/// @var Config
- config: Config,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
/// @var array<Job>
jobs: IndexMap<i64, Job>,
/// @var mixed[]
@@ -88,13 +88,12 @@ impl HttpDownloader {
/// @param mixed[] $options The options
pub fn new(
io: Box<dyn IOInterface>,
- config: Config,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
options: IndexMap<String, PhpMixed>,
disable_tls: bool,
) -> Self {
let disabled = Platform::get_env("COMPOSER_DISABLE_NETWORK")
- .as_bool()
- .unwrap_or(false);
+ .map_or(false, |s| !s.is_empty() && s != "0");
// Setup TLS options
// The cafile option can be set via config.json
@@ -125,8 +124,8 @@ impl HttpDownloader {
let curl = if Self::is_curl_enabled() {
Some(CurlDownloader::new(
- &*io,
- &config,
+ io.clone_box(),
+ std::rc::Rc::clone(&config),
options.clone(),
disable_tls,
))
@@ -135,8 +134,8 @@ impl HttpDownloader {
};
let rfs = Some(RemoteFilesystem::new(
- &*io,
- &config,
+ io.clone_box(),
+ std::rc::Rc::clone(&config),
options.clone(),
disable_tls,
));
@@ -328,7 +327,7 @@ impl HttpDownloader {
let id = self.id_gen;
self.id_gen += 1;
- let origin = Url::get_origin(&self.config, &request.url);
+ let origin = Url::get_origin(&*self.config.borrow(), &request.url);
let job = Job {
id,
@@ -354,13 +353,28 @@ impl HttpDownloader {
}
// capture username/password from URL if there is one
- if let Some(m) =
- Preg::is_match_strict_groups(r"{^https?://([^:/]+):([^@/]+)@([^/]+)}i", &request.url)
+ let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::is_match_strict_groups3(
+ r"{^https?://([^:/]+):([^@/]+)@([^/]+)}i",
+ &request.url,
+ Some(&mut m),
+ )
+ .unwrap_or(false)
{
self.io.set_authentication(
origin.clone(),
- rawurldecode(m.get(1).cloned().unwrap_or_default().as_str()),
- Some(rawurldecode(m.get(2).cloned().unwrap_or_default().as_str())),
+ rawurldecode(
+ m.get(&CaptureKey::ByIndex(1))
+ .cloned()
+ .unwrap_or_default()
+ .as_str(),
+ ),
+ Some(rawurldecode(
+ m.get(&CaptureKey::ByIndex(2))
+ .cloned()
+ .unwrap_or_default()
+ .as_str(),
+ )),
);
}
@@ -723,7 +737,7 @@ impl HttpDownloader {
return false;
}
- if !Preg::is_match(r"{^https?://}i", &job.request.url) {
+ if !Preg::is_match(r"{^https?://}i", &job.request.url).unwrap_or(false) {
return false;
}
diff --git a/crates/shirabe/src/util/loop.rs b/crates/shirabe/src/util/loop.rs
index 189b8a3..9ffee8f 100644
--- a/crates/shirabe/src/util/loop.rs
+++ b/crates/shirabe/src/util/loop.rs
@@ -10,21 +10,21 @@ use shirabe_php_shim::microtime;
#[derive(Debug)]
pub struct Loop {
- http_downloader: HttpDownloader,
- process_executor: Option<ProcessExecutor>,
+ http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>,
+ process_executor: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>,
current_promises: IndexMap<i64, Vec<Box<dyn PromiseInterface>>>,
wait_index: i64,
}
impl Loop {
pub fn new(
- mut http_downloader: HttpDownloader,
- process_executor: Option<ProcessExecutor>,
+ http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>,
+ process_executor: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>,
) -> Self {
- http_downloader.enable_async();
+ http_downloader.borrow_mut().enable_async();
- let process_executor = process_executor.map(|mut pe| {
- pe.enable_async();
+ let process_executor = process_executor.map(|pe| {
+ pe.borrow_mut().enable_async();
pe
});
@@ -36,11 +36,13 @@ impl Loop {
}
}
- pub fn get_http_downloader(&self) -> &HttpDownloader {
+ pub fn get_http_downloader(&self) -> &std::rc::Rc<std::cell::RefCell<HttpDownloader>> {
&self.http_downloader
}
- pub fn get_process_executor(&self) -> Option<&ProcessExecutor> {
+ pub fn get_process_executor(
+ &self,
+ ) -> Option<&std::rc::Rc<std::cell::RefCell<ProcessExecutor>>> {
self.process_executor.as_ref()
}
@@ -66,9 +68,9 @@ impl Loop {
if let Some(ref progress) = progress {
let mut total_jobs: i64 = 0;
- total_jobs += self.http_downloader.count_active_jobs();
+ total_jobs += self.http_downloader.borrow_mut().count_active_jobs(None);
if let Some(ref pe) = self.process_executor {
- total_jobs += pe.count_active_jobs();
+ total_jobs += pe.borrow_mut().count_active_jobs(None);
}
progress.start(total_jobs);
}
@@ -77,9 +79,9 @@ impl Loop {
loop {
let mut active_jobs: i64 = 0;
- active_jobs += self.http_downloader.count_active_jobs();
+ active_jobs += self.http_downloader.borrow_mut().count_active_jobs(None);
if let Some(ref pe) = self.process_executor {
- active_jobs += pe.count_active_jobs();
+ active_jobs += pe.borrow_mut().count_active_jobs(None);
}
if let Some(ref progress) = progress {
diff --git a/crates/shirabe/src/util/perforce.rs b/crates/shirabe/src/util/perforce.rs
index a647dbb..d4d39ea 100644
--- a/crates/shirabe/src/util/perforce.rs
+++ b/crates/shirabe/src/util/perforce.rs
@@ -29,12 +29,12 @@ pub struct Perforce {
pub(crate) p4_client_spec: String,
pub(crate) p4_depot_type: Option<String>,
pub(crate) p4_branch: Option<String>,
- pub(crate) process: ProcessExecutor,
+ pub(crate) process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
pub(crate) unique_perforce_client_name: String,
pub(crate) windows_flag: bool,
pub(crate) command_result: String,
pub(crate) io: Box<dyn IOInterface>,
- pub(crate) filesystem: Option<Filesystem>,
+ pub(crate) filesystem: Option<std::rc::Rc<std::cell::RefCell<Filesystem>>>,
}
impl Perforce {
@@ -43,7 +43,7 @@ impl Perforce {
repo_config: IndexMap<String, PhpMixed>,
port: String,
path: String,
- process: ProcessExecutor,
+ process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
is_windows: bool,
io: Box<dyn IOInterface>,
) -> Self {
@@ -75,7 +75,7 @@ impl Perforce {
repo_config: IndexMap<String, PhpMixed>,
port: String,
path: String,
- process: ProcessExecutor,
+ process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
io: Box<dyn IOInterface>,
) -> Self {
Self::new(repo_config, port, path, process, Platform::is_windows(), io)
@@ -83,7 +83,7 @@ impl Perforce {
pub fn check_server_exists(url: &str, process_executor: &mut ProcessExecutor) -> bool {
let mut ignored_output = String::new();
- process_executor.execute(
+ process_executor.execute_args(
&vec![
"p4".to_string(),
"-p".to_string(),
@@ -152,7 +152,7 @@ impl Perforce {
));
let client_spec = self.get_p4_client_spec();
let file_system = self.get_filesystem();
- file_system.remove(&client_spec);
+ file_system.borrow_mut().remove(&client_spec);
}
/// @param non-empty-string|non-empty-list<string> $command
@@ -168,7 +168,8 @@ impl Perforce {
_ => vec![],
};
self.process
- .execute(&cmd_vec, &mut self.command_result, None)
+ .borrow_mut()
+ .execute_args(&cmd_vec, &mut self.command_result, None)
}
pub fn get_client(&mut self) -> String {
@@ -195,7 +196,7 @@ impl Perforce {
pub fn initialize_path(&mut self, path: &str) {
self.path = path.to_string();
let fs = self.get_filesystem();
- fs.ensure_directory_exists(path);
+ fs.borrow_mut().ensure_directory_exists(path);
}
pub(crate) fn get_port(&self) -> &str {
@@ -361,7 +362,7 @@ impl Perforce {
.collect(),
));
if exit_code != 0 {
- let error_output = self.process.get_error_output().to_string();
+ let error_output = self.process.borrow().get_error_output().to_string();
let user = self.get_user().unwrap_or_default();
let index = strpos(&error_output, &user);
if index.is_none() {
@@ -401,7 +402,7 @@ impl Perforce {
file_get_contents(&self.get_p4_client_spec()),
None,
);
- process.run(None, IndexMap::new());
+ process.run(None);
}
pub fn sync_code_base(&mut self, source_reference: Option<&str>) -> Result<()> {
@@ -558,7 +559,7 @@ impl Perforce {
None,
);
- process.run(None, IndexMap::new())
+ process.run(None)
}
pub fn p4_login(&mut self) -> Result<()> {
@@ -583,11 +584,14 @@ impl Perforce {
password,
None,
);
- process.run(None, IndexMap::new());
+ process.run(None);
if !process.is_successful() {
return Err(Exception {
- message: format!("Error logging in:{}", self.process.get_error_output()),
+ message: format!(
+ "Error logging in:{}",
+ self.process.borrow().get_error_output()
+ ),
code: 0,
}
.into());
@@ -847,15 +851,17 @@ impl Perforce {
Some(self.command_result.clone())
}
- pub fn get_filesystem(&mut self) -> &Filesystem {
+ pub fn get_filesystem(&mut self) -> &std::rc::Rc<std::cell::RefCell<Filesystem>> {
if self.filesystem.is_none() {
- self.filesystem = Some(Filesystem::new(&self.process));
+ self.filesystem = Some(std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new(
+ Some(std::rc::Rc::clone(&self.process)),
+ ))));
}
self.filesystem.as_ref().unwrap()
}
- pub fn set_filesystem(&mut self, fs: Filesystem) {
+ pub fn set_filesystem(&mut self, fs: std::rc::Rc<std::cell::RefCell<Filesystem>>) {
self.filesystem = Some(fs);
}
diff --git a/crates/shirabe/src/util/process_executor.rs b/crates/shirabe/src/util/process_executor.rs
index ed1958d..d0410ff 100644
--- a/crates/shirabe/src/util/process_executor.rs
+++ b/crates/shirabe/src/util/process_executor.rs
@@ -5,7 +5,7 @@ use anyhow::Result;
use indexmap::IndexMap;
use std::sync::{LazyLock, Mutex};
-use shirabe_external_packages::composer::pcre::preg::Preg;
+use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg};
use shirabe_external_packages::react::promise::promise::Promise;
use shirabe_external_packages::react::promise::promise_interface::PromiseInterface;
use shirabe_external_packages::seld::signal::signal_handler::SignalHandler;
@@ -79,7 +79,7 @@ impl ProcessExecutor {
const GIT_CMDS_NEED_GIT_DIR: &'static [&'static [&'static str]] =
&[&["show"], &["log"], &["branch"], &["remote", "set-url"]];
- pub fn new(io: Option<Box<dyn IOInterface>>, _: Option<()>) -> Self {
+ pub fn new(io: Option<Box<dyn IOInterface>>) -> Self {
let mut this = Self {
capture_output: false,
error_output: String::new(),
@@ -116,6 +116,28 @@ impl ProcessExecutor {
self.do_execute(command, cwd, false, None)
}
+ /// Convenience wrapper used by phase-A code that calls
+ /// `process.execute(&[String], &mut String, Option<&str>) == 0`.
+ /// Forwards to `execute`, returning the status code (0 on Err for compatibility).
+ pub fn execute_args<C: AsRef<str>>(
+ &mut self,
+ command: &[String],
+ output: &mut String,
+ cwd: Option<C>,
+ ) -> i64 {
+ let cmd = PhpMixed::List(
+ command
+ .iter()
+ .map(|s| Box::new(PhpMixed::String(s.clone())))
+ .collect(),
+ );
+ let mut buf = PhpMixed::String(String::new());
+ let cwd_str: Option<&str> = cwd.as_ref().map(|s| s.as_ref());
+ let rc = self.execute(cmd, Some(&mut buf), cwd_str).unwrap_or(1);
+ *output = buf.as_string().unwrap_or("").to_string();
+ rc
+ }
+
/// runs a process on the commandline in TTY mode
pub fn execute_tty(&mut self, command: PhpMixed, cwd: Option<&str>) -> Result<i64> {
if Platform::is_tty(None) {
@@ -142,14 +164,16 @@ impl ProcessExecutor {
if is_string(&command) {
let mut command_str = command.as_string().unwrap_or("").to_string();
if Platform::is_windows() {
- if let Some(m) = Preg::is_match_strict_groups(r"{^([^:/\\]++) }", &command_str) {
+ let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::is_match_strict_groups3(r"{^([^:/\\]++) }", &command_str, Some(&mut m))
+ .unwrap_or(false)
+ {
+ let m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default();
command_str = substr_replace(
&command_str,
- &Self::escape(PhpMixed::String(Self::get_executable(
- m.get(1).cloned().unwrap_or_default().as_str(),
- ))),
+ &Self::escape(PhpMixed::String(Self::get_executable(&m1))),
0,
- strlen(m.get(1).cloned().unwrap_or_default().as_str()) as usize,
+ strlen(&m1) as usize,
);
}
}
@@ -359,17 +383,15 @@ impl ProcessExecutor {
}
if Process::ERR == r#type {
- self.io.as_ref().unwrap().write_error_raw(
- PhpMixed::String(buffer.to_string()),
- false,
- io_interface::NORMAL,
- );
+ self.io
+ .as_mut()
+ .unwrap()
+ .write_error_raw3(buffer, false, io_interface::NORMAL);
} else {
- self.io.as_ref().unwrap().write_raw(
- PhpMixed::String(buffer.to_string()),
- false,
- io_interface::NORMAL,
- );
+ self.io
+ .as_mut()
+ .unwrap()
+ .write_raw3(buffer, false, io_interface::NORMAL);
}
}
@@ -600,13 +622,17 @@ impl ProcessExecutor {
if Preg::is_match(
GitHub::GITHUB_TOKEN_REGEX,
m.get("user").cloned().unwrap_or_default().as_str(),
- ) {
+ )
+ .unwrap_or(false)
+ {
return "://***:***@".to_string();
}
if Preg::is_match(
r"{^[a-f0-9]{12,}$}",
m.get("user").cloned().unwrap_or_default().as_str(),
- ) {
+ )
+ .unwrap_or(false)
+ {
return "://***:***@".to_string();
}
@@ -668,7 +694,7 @@ impl ProcessExecutor {
// PHP: Preg::replace('/(\\\\*)"/', '$1$1\\"', $argument, -1, $dquotes)
argument =
Preg::replace_with_count(r#"/(\\*)"/"#, r#"$1$1\""#, &argument, -1, &mut dquotes);
- let meta = dquotes > 0 || Preg::is_match(r"/%[^%]+%|![^!]+!/", &argument);
+ let meta = dquotes > 0 || Preg::is_match(r"/%[^%]+%|![^!]+!/", &argument).unwrap_or(false);
if !meta && !quote {
quote = strpbrk(&argument, "^&|<>()").is_some();
diff --git a/crates/shirabe/src/util/remote_filesystem.rs b/crates/shirabe/src/util/remote_filesystem.rs
index d2658fe..7c10640 100644
--- a/crates/shirabe/src/util/remote_filesystem.rs
+++ b/crates/shirabe/src/util/remote_filesystem.rs
@@ -2,7 +2,7 @@
use indexmap::IndexMap;
-use shirabe_external_packages::composer::pcre::preg::Preg;
+use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg};
use shirabe_php_shim::{
FILTER_VALIDATE_BOOLEAN, PHP_URL_HOST, PHP_URL_PATH, PHP_VERSION_ID, PhpMixed,
RuntimeException, array_replace_recursive, base64_encode, explode, extension_loaded,
@@ -33,7 +33,7 @@ pub enum GetResult {
#[derive(Debug)]
pub struct RemoteFilesystem {
io: Box<dyn IOInterface>,
- config: Config,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
scheme: String,
bytes_max: i64,
origin_url: String,
@@ -55,7 +55,7 @@ pub struct RemoteFilesystem {
impl RemoteFilesystem {
pub fn new(
io: Box<dyn IOInterface>,
- config: Config,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
options: IndexMap<String, PhpMixed>,
disable_tls: bool,
auth_helper: Option<AuthHelper>,
@@ -140,8 +140,14 @@ impl RemoteFilesystem {
pub fn find_status_code(headers: &[String]) -> Option<i64> {
let mut value: Option<i64> = None;
for header in headers {
- if let Ok(Some(m)) = Preg::is_match_strict_groups("{^HTTP/\\S+ (\\d+)}i", header) {
- value = Some(m["1"].parse().unwrap_or(0));
+ let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::is_match_strict_groups3("{^HTTP/\\S+ (\\d+)}i", header, Some(&mut m))
+ .unwrap_or(false)
+ {
+ value = m
+ .get(&CaptureKey::ByIndex(1))
+ .and_then(|s| s.parse().ok())
+ .or(Some(0));
}
}
@@ -251,8 +257,8 @@ impl RemoteFilesystem {
let proxy = ProxyManager::get_instance().get_proxy_for_request(&file_url);
let using_proxy = proxy.get_status(" using proxy (%s)");
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"{}{}{}",
if strpos(&orig_file_url, "http") == Some(0) {
"Downloading "
@@ -261,7 +267,7 @@ impl RemoteFilesystem {
},
Url::sanitize(&orig_file_url),
using_proxy
- )),
+ ),
true,
crate::io::io_interface::DEBUG,
);
@@ -270,12 +276,16 @@ impl RemoteFilesystem {
|| (strpos(&file_url, "$").is_none() && strpos(&file_url, "%24").is_none()))
&& !degraded_packagist
{
- self.config.prohibit_url_by_config(&file_url, &*self.io);
+ let _ = self.config.borrow_mut().prohibit_url_by_config(
+ &file_url,
+ Some(&*self.io),
+ &indexmap::IndexMap::new(),
+ );
}
if self.progress && !is_redirect {
- self.io.write_error(
- PhpMixed::String("Downloading (<comment>connecting...</comment>)".to_string()),
+ self.io.write_error3(
+ "Downloading (<comment>connecting...</comment>)",
false,
crate::io::io_interface::NORMAL,
);
@@ -344,13 +354,13 @@ impl RemoteFilesystem {
.unwrap_or_else(|_| self.normalize_result(result.as_deref()));
e.set_response(decoded);
- self.io.write_error(
- PhpMixed::String(format!(
+ self.io.write_error3(
+ &format!(
"Content-Length mismatch, received {} out of {} bytes: ({})",
Platform::strlen(result.as_deref().unwrap_or("")),
cl_int,
base64_encode(result.as_deref().unwrap_or(""))
- )),
+ ),
true,
crate::io::io_interface::DEBUG,
);
@@ -395,22 +405,15 @@ impl RemoteFilesystem {
let msg_owned = format!("{}", e);
if !self.degraded_mode && strpos(&msg_owned, "Operation timed out").is_some() {
self.degraded_mode = true;
- self.io.write_error(
- PhpMixed::String("".to_string()),
- true,
- crate::io::io_interface::NORMAL,
- );
- self.io.write_error(
- PhpMixed::List(vec![
- Box::new(PhpMixed::String(format!("<error>{}</error>", msg_owned))),
- Box::new(PhpMixed::String(
- "<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>"
- .to_string(),
- )),
- ]),
- true,
- crate::io::io_interface::NORMAL,
- );
+ self.io
+ .write_error3("", true, crate::io::io_interface::NORMAL);
+ self.io.write_error3(PhpMixed::List(vec![
+ Box::new(PhpMixed::String(format!("<error>{}</error>", msg_owned))),
+ Box::new(PhpMixed::String(
+ "<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>"
+ .to_string(),
+ )),
+ ]), true, crate::io::io_interface::NORMAL);
return self.get(
&self.origin_url.clone(),
@@ -460,6 +463,7 @@ impl RemoteFilesystem {
let gitlab_domains: Vec<String> = self
.config
+ .borrow_mut()
.get("gitlab-domains")
.and_then(|v| v.as_list())
.map(|l| {
@@ -494,8 +498,8 @@ impl RemoteFilesystem {
if code >= 400 && code <= 599 {
if !self.retry {
if self.progress && !is_redirect {
- self.io.overwrite_error(
- PhpMixed::String("Downloading (<error>failed</error>)".to_string()),
+ self.io.overwrite_error4(
+ "Downloading (<error>failed</error>)",
false,
None,
crate::io::io_interface::NORMAL,
@@ -522,15 +526,15 @@ impl RemoteFilesystem {
}
if self.progress && !self.retry && !is_redirect {
- self.io.overwrite_error(
- PhpMixed::String(format!(
+ self.io.overwrite_error4(
+ &format!(
"Downloading ({})",
if result.is_none() {
"<error>failed</error>"
} else {
"<comment>100%</comment>"
}
- )),
+ ),
false,
None,
crate::io::io_interface::NORMAL,
@@ -552,21 +556,17 @@ impl RemoteFilesystem {
}
self.degraded_mode = true;
- self.io.write_error(
- PhpMixed::List(vec![
- Box::new(PhpMixed::String("".to_string())),
- Box::new(PhpMixed::String(format!(
- "<error>Failed to decode response: {}</error>",
- e
- ))),
- Box::new(PhpMixed::String(
- "<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>"
- .to_string(),
- )),
- ]),
- true,
- crate::io::io_interface::NORMAL,
- );
+ self.io.write_error3(PhpMixed::List(vec![
+ Box::new(PhpMixed::String("".to_string())),
+ Box::new(PhpMixed::String(format!(
+ "<error>Failed to decode response: {}</error>",
+ e
+ ))),
+ Box::new(PhpMixed::String(
+ "<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>"
+ .to_string(),
+ )),
+ ]), true, crate::io::io_interface::NORMAL);
return self.get(
&self.origin_url.clone(),
@@ -640,22 +640,15 @@ impl RemoteFilesystem {
let msg_owned = format!("{}", e);
if !self.degraded_mode && strpos(&msg_owned, "Operation timed out").is_some() {
self.degraded_mode = true;
- self.io.write_error(
- PhpMixed::String("".to_string()),
- true,
- crate::io::io_interface::NORMAL,
- );
- self.io.write_error(
- PhpMixed::List(vec![
- Box::new(PhpMixed::String(format!("<error>{}</error>", msg_owned))),
- Box::new(PhpMixed::String(
- "<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>"
- .to_string(),
- )),
- ]),
- true,
- crate::io::io_interface::NORMAL,
- );
+ self.io
+ .write_error3("", true, crate::io::io_interface::NORMAL);
+ self.io.write_error3(PhpMixed::List(vec![
+ Box::new(PhpMixed::String(format!("<error>{}</error>", msg_owned))),
+ Box::new(PhpMixed::String(
+ "<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>"
+ .to_string(),
+ )),
+ ]), true, crate::io::io_interface::NORMAL);
return self.get(
&self.origin_url.clone(),
@@ -771,11 +764,8 @@ impl RemoteFilesystem {
&& Some(progression) != self.last_progress
{
self.last_progress = Some(progression);
- self.io.overwrite_error(
- PhpMixed::String(format!(
- "Downloading (<comment>{}%</comment>)",
- progression
- )),
+ self.io.overwrite_error4(
+ &format!("Downloading (<comment>{}%</comment>)", progression),
false,
None,
crate::io::io_interface::NORMAL,
@@ -944,19 +934,16 @@ impl RemoteFilesystem {
if let Some(target_url) = target_url {
self.redirects += 1;
- self.io.write_error(
- PhpMixed::String("".to_string()),
- true,
- crate::io::io_interface::DEBUG,
- );
- self.io.write_error(
- PhpMixed::String(sprintf(
+ self.io
+ .write_error3("", true, crate::io::io_interface::DEBUG);
+ self.io.write_error3(
+ &sprintf(
"Following redirect (%u) %s",
&[
PhpMixed::Int(self.redirects),
PhpMixed::String(Url::sanitize(&target_url)),
],
- )),
+ ),
true,
crate::io::io_interface::DEBUG,
);
diff --git a/crates/shirabe/src/util/svn.rs b/crates/shirabe/src/util/svn.rs
index 955e28c..107be1b 100644
--- a/crates/shirabe/src/util/svn.rs
+++ b/crates/shirabe/src/util/svn.rs
@@ -5,7 +5,7 @@ use std::sync::Mutex;
use anyhow::Result;
use indexmap::IndexMap;
-use shirabe_external_packages::composer::pcre::preg::Preg;
+use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg};
use shirabe_php_shim::{
LogicException, PHP_URL_HOST, PhpMixed, RuntimeException, empty, implode, parse_url,
parse_url_all, stripos, strpos, trim,
@@ -35,11 +35,11 @@ pub struct Svn {
/// @var bool
pub(crate) cache_credentials: bool,
/// @var ProcessExecutor
- pub(crate) process: ProcessExecutor,
+ pub(crate) process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>,
/// @var int
pub(crate) qty_auth_tries: i64,
/// @var Config
- pub(crate) config: Config,
+ pub(crate) config: std::rc::Rc<std::cell::RefCell<Config>>,
}
/// @var string|null
@@ -51,10 +51,12 @@ impl Svn {
pub fn new(
url: String,
io: Box<dyn IOInterface>,
- config: Config,
- process: Option<ProcessExecutor>,
+ config: std::rc::Rc<std::cell::RefCell<Config>>,
+ process: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>,
) -> Self {
- let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io));
+ let process = process.unwrap_or_else(|| {
+ std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(&*io)))
+ });
Self {
url,
io,
@@ -91,7 +93,11 @@ impl Svn {
verbose: bool,
) -> Result<String> {
// Ensure we are allowed to use this URL by config
- self.config.prohibit_url_by_config(url, &*self.io)?;
+ self.config.borrow_mut().prohibit_url_by_config(
+ url,
+ Some(&*self.io),
+ &indexmap::IndexMap::new(),
+ )?;
self.execute_with_auth_retry(command, cwd, url, path, verbose)
.map(|o| o.unwrap_or_default())
@@ -149,14 +155,16 @@ impl Svn {
};
// TODO(phase-b): pass handler callback to process.execute
let mut handler_output = String::new();
- let status = self
- .process
- .execute(&command, &mut handler_output, cwd.map(String::from));
+ let status = self.process.borrow_mut().execute_args(
+ &command,
+ &mut handler_output,
+ cwd.map(String::from),
+ );
if 0 == status {
return Ok(output);
}
- let error_output = self.process.get_error_output();
+ let error_output = self.process.borrow().get_error_output();
let full_output = trim(
&implode("\n", &[output.clone().unwrap_or_default(), error_output]),
None,
@@ -341,12 +349,12 @@ impl Svn {
/// Create the auth params from the configuration file.
fn create_auth_from_config(&mut self) -> bool {
- if !self.config.has("http-basic") {
+ if !self.config.borrow().has("http-basic") {
self.has_auth = Some(false);
return false;
}
- let auth_config = self.config.get("http-basic");
+ let auth_config = self.config.borrow_mut().get("http-basic");
let host = parse_url(&self.url, PHP_URL_HOST);
let host_str = host.as_string().unwrap_or("");
@@ -419,16 +427,21 @@ impl Svn {
let mut cached = VERSION.lock().unwrap();
if cached.is_none() {
let mut output = String::new();
- if 0 == self.process.execute(
+ if 0 == self.process.borrow_mut().execute_args(
&["svn".to_string(), "--version".to_string()],
&mut output,
None,
) {
- // TODO(phase-b): Preg::is_match with captures should populate $match
- if let Ok(Some(matches)) =
- Preg::is_match_with_indexed_captures(r"{(\d+(?:\.\d+)+)}", &output)
+ let mut matches: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::is_match3(r"{(\d+(?:\.\d+)+)}", &output, Some(&mut matches))
+ .unwrap_or(false)
{
- *cached = Some(matches.get(1).cloned().unwrap_or_default());
+ *cached = Some(
+ matches
+ .get(&CaptureKey::ByIndex(1))
+ .cloned()
+ .unwrap_or_default(),
+ );
}
}
}
diff --git a/crates/shirabe/src/util/sync_helper.rs b/crates/shirabe/src/util/sync_helper.rs
index 22e4f1f..7d69db3 100644
--- a/crates/shirabe/src/util/sync_helper.rs
+++ b/crates/shirabe/src/util/sync_helper.rs
@@ -18,9 +18,9 @@ impl<'a> DownloaderOrManager<'a> {
package: &dyn PackageInterface,
path: &str,
prev_package: Option<&dyn PackageInterface>,
- ) -> Box<dyn PromiseInterface> {
+ ) -> Result<Box<dyn PromiseInterface>> {
match self {
- Self::Interface(d) => d.download(package, path, prev_package),
+ Self::Interface(d) => d.download3(package, path, prev_package),
Self::Manager(d) => d.borrow().download(package, path, prev_package),
}
}
@@ -31,16 +31,20 @@ impl<'a> DownloaderOrManager<'a> {
package: &dyn PackageInterface,
path: &str,
prev_package: Option<&dyn PackageInterface>,
- ) -> Box<dyn PromiseInterface> {
+ ) -> Result<Box<dyn PromiseInterface>> {
match self {
Self::Interface(d) => d.prepare(r#type, package, path, prev_package),
Self::Manager(d) => d.borrow().prepare(r#type, package, path, prev_package),
}
}
- fn install(&self, package: &dyn PackageInterface, path: &str) -> Box<dyn PromiseInterface> {
+ fn install(
+ &self,
+ package: &dyn PackageInterface,
+ path: &str,
+ ) -> Result<Box<dyn PromiseInterface>> {
match self {
- Self::Interface(d) => d.install(package, path),
+ Self::Interface(d) => d.install2(package, path),
Self::Manager(d) => d.borrow().install(package, path),
}
}
@@ -50,7 +54,7 @@ impl<'a> DownloaderOrManager<'a> {
package: &dyn PackageInterface,
prev_package: &dyn PackageInterface,
path: &str,
- ) -> Box<dyn PromiseInterface> {
+ ) -> Result<Box<dyn PromiseInterface>> {
match self {
Self::Interface(d) => d.update(package, prev_package, path),
Self::Manager(d) => d.borrow().update(package, prev_package, path),
@@ -63,7 +67,7 @@ impl<'a> DownloaderOrManager<'a> {
package: &dyn PackageInterface,
path: &str,
prev_package: Option<&dyn PackageInterface>,
- ) -> Box<dyn PromiseInterface> {
+ ) -> Result<Box<dyn PromiseInterface>> {
match self {
Self::Interface(d) => d.cleanup(r#type, package, path, prev_package),
Self::Manager(d) => d.borrow().cleanup(r#type, package, path, prev_package),
@@ -90,18 +94,18 @@ impl SyncHelper {
let result: Result<()> = (|| {
Self::r#await(
r#loop,
- Some(downloader.download(package, &path, prev_package)),
+ Some(downloader.download(package, &path, prev_package)?),
)?;
Self::r#await(
r#loop,
- Some(downloader.prepare(r#type, package, &path, prev_package)),
+ Some(downloader.prepare(r#type, package, &path, prev_package)?),
)?;
if r#type == "update" {
if let Some(prev) = prev_package {
- Self::r#await(r#loop, Some(downloader.update(package, prev, &path)))?;
+ Self::r#await(r#loop, Some(downloader.update(package, prev, &path)?))?;
}
} else {
- Self::r#await(r#loop, Some(downloader.install(package, &path)))?;
+ Self::r#await(r#loop, Some(downloader.install(package, &path)?))?;
}
Ok(())
})();
@@ -109,14 +113,14 @@ impl SyncHelper {
if result.is_err() {
Self::r#await(
r#loop,
- Some(downloader.cleanup(r#type, package, &path, prev_package)),
+ Some(downloader.cleanup(r#type, package, &path, prev_package)?),
)?;
return result;
}
Self::r#await(
r#loop,
- Some(downloader.cleanup(r#type, package, &path, prev_package)),
+ Some(downloader.cleanup(r#type, package, &path, prev_package)?),
)?;
Ok(())
}
diff --git a/crates/shirabe/src/util/url.rs b/crates/shirabe/src/util/url.rs
index 7d0ab21..7dc6e4f 100644
--- a/crates/shirabe/src/util/url.rs
+++ b/crates/shirabe/src/util/url.rs
@@ -2,7 +2,8 @@
use crate::config::Config;
use crate::util::github::GitHub;
-use shirabe_external_packages::composer::pcre::preg::Preg;
+use indexmap::IndexMap;
+use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg};
use shirabe_php_shim::{PHP_URL_HOST, PHP_URL_PORT, PhpMixed, in_array, parse_url};
pub struct Url;
@@ -15,62 +16,80 @@ impl Url {
.unwrap_or_default();
if host == "api.github.com" || host == "github.com" || host == "www.github.com" {
- if let Some(m) = Preg::match_(
+ let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::match3(
r"(?i)^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$",
&url,
- ) {
+ Some(&mut m),
+ )
+ .unwrap_or(false)
+ {
url = format!(
"https://api.github.com/repos/{}/{}/{}ball/{}",
- m.get("1").unwrap_or(&String::new()),
- m.get("2").unwrap_or(&String::new()),
- m.get("3").unwrap_or(&String::new()),
+ m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(),
r#ref
);
- } else if let Some(m) = Preg::match_(
+ } else if Preg::match3(
r"(?i)^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/archive/.+\.(zip|tar)(?:\.gz)?$",
&url,
- ) {
+ Some(&mut m),
+ )
+ .unwrap_or(false)
+ {
url = format!(
"https://api.github.com/repos/{}/{}/{}ball/{}",
- m.get("1").unwrap_or(&String::new()),
- m.get("2").unwrap_or(&String::new()),
- m.get("3").unwrap_or(&String::new()),
+ m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(),
r#ref
);
- } else if let Some(m) = Preg::match_(
+ } else if Preg::match3(
r"(?i)^https?://api\.github\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$",
&url,
- ) {
+ Some(&mut m),
+ )
+ .unwrap_or(false)
+ {
url = format!(
"https://api.github.com/repos/{}/{}/{}ball/{}",
- m.get("1").unwrap_or(&String::new()),
- m.get("2").unwrap_or(&String::new()),
- m.get("3").unwrap_or(&String::new()),
+ m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(),
r#ref
);
}
} else if host == "bitbucket.org" || host == "www.bitbucket.org" {
- if let Some(m) = Preg::match_(
+ let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::match3(
r"(?i)^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$",
&url,
- ) {
+ Some(&mut m),
+ )
+ .unwrap_or(false)
+ {
url = format!(
"https://bitbucket.org/{}/{}/get/{}.{}",
- m.get("1").unwrap_or(&String::new()),
- m.get("2").unwrap_or(&String::new()),
+ m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
r#ref,
- m.get("4").unwrap_or(&String::new())
+ m.get(&CaptureKey::ByIndex(4)).cloned().unwrap_or_default()
);
}
} else if host == "gitlab.com" || host == "www.gitlab.com" {
- if let Some(m) = Preg::match_(
+ let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
+ if Preg::match3(
r"(?i)^https?://(?:www\.)?gitlab\.com/api/v[34]/projects/([^/]+)/repository/archive\.(zip|tar\.gz|tar\.bz2|tar)\?sha=.+$",
&url,
- ) {
+ Some(&mut m),
+ )
+ .unwrap_or(false)
+ {
url = format!(
"https://gitlab.com/api/v4/projects/{}/repository/archive.{}?sha={}",
- m.get("1").unwrap_or(&String::new()),
- m.get("2").unwrap_or(&String::new()),
+ m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
r#ref
);
}
@@ -82,8 +101,9 @@ impl Url {
url = Preg::replace(
r"(?i)(/repos/[^/]+/[^/]+/(zip|tar)ball)(?:/.+)?$",
&format!("$1/{}", r#ref),
- url,
- );
+ &url,
+ )
+ .unwrap_or(url);
} else if in_array(
PhpMixed::String(host.clone()),
&config.get("gitlab-domains"),
@@ -92,8 +112,9 @@ impl Url {
url = Preg::replace(
r"(?i)(/api/v[34]/projects/[^/]+/repository/archive\.(?:zip|tar\.gz|tar\.bz2|tar)\?sha=).+$",
&format!("${{1}}{}", r#ref),
- url,
- );
+ &url,
+ )
+ .unwrap_or(url);
}
assert!(!url.is_empty());
@@ -148,30 +169,29 @@ impl Url {
pub fn sanitize(url: String) -> String {
// GitHub repository rename result in redirect locations containing the access_token as GET parameter
// e.g. https://api.github.com/repositories/9999999999?access_token=github_token
- let url = Preg::replace(r"([&?]access_token=)[^&]+", "$1***", url);
+ let url = Preg::replace(r"([&?]access_token=)[^&]+", "$1***", &url).unwrap_or(url);
let url = Preg::replace_callback(
r"(?i)^(?P<prefix>[a-z0-9]+://)?(?P<user>[^:/\s@]+):(?P<password>[^@\s/]+)@",
|m| {
+ let user = m
+ .get(&CaptureKey::ByName("user".to_string()))
+ .cloned()
+ .unwrap_or_default();
+ let prefix = m
+ .get(&CaptureKey::ByName("prefix".to_string()))
+ .cloned()
+ .unwrap_or_default();
// if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that
- if Preg::is_match(
- GitHub::GITHUB_TOKEN_REGEX,
- m.get("user").map(|s| s.as_str()).unwrap_or(""),
- ) {
- format!(
- "{}***:***@",
- m.get("prefix").map(|s| s.as_str()).unwrap_or("")
- )
+ if Preg::is_match(GitHub::GITHUB_TOKEN_REGEX, &user).unwrap_or(false) {
+ format!("{}***:***@", prefix)
} else {
- format!(
- "{}{}:***@",
- m.get("prefix").map(|s| s.as_str()).unwrap_or(""),
- m.get("user").map(|s| s.as_str()).unwrap_or("")
- )
+ format!("{}{}:***@", prefix, user)
}
},
- url,
- );
+ &url,
+ )
+ .unwrap_or(url);
url
}