aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/shirabe/src/downloader/git_downloader.rs6
-rw-r--r--crates/shirabe/src/repository/vcs/git_driver.rs2
-rw-r--r--crates/shirabe/src/util/git.rs151
-rw-r--r--crates/shirabe/tests/util/git_test.rs12
4 files changed, 134 insertions, 37 deletions
diff --git a/crates/shirabe/src/downloader/git_downloader.rs b/crates/shirabe/src/downloader/git_downloader.rs
index 55cf161..cbd429c 100644
--- a/crates/shirabe/src/downloader/git_downloader.rs
+++ b/crates/shirabe/src/downloader/git_downloader.rs
@@ -974,7 +974,7 @@ impl VcsDownloader for GitDownloader {
self.inner.io.write_error3(&msg, true, io_interface::NORMAL);
self.git_util
- .run_commands(commands, url, Some(&path), true, None)?;
+ .run_commands(commands, url, Some(&path), true, ())?;
let source_url = package.get_source_url();
if Some(url) != source_url.as_deref() && source_url.is_some() {
@@ -1092,7 +1092,7 @@ impl VcsDownloader for GitDownloader {
];
self.git_util
- .run_commands(commands, url, Some(&path), false, None)?;
+ .run_commands(commands, url, Some(&path), false, ())?;
}
let command = vec![
@@ -1104,7 +1104,7 @@ impl VcsDownloader for GitDownloader {
"%sanitizedUrl%".to_string(),
];
self.git_util
- .run_commands(vec![command], url, Some(&path), false, None)?;
+ .run_commands(vec![command], url, Some(&path), false, ())?;
let pretty_version = target.get_pretty_version();
if let Some(new_ref) =
diff --git a/crates/shirabe/src/repository/vcs/git_driver.rs b/crates/shirabe/src/repository/vcs/git_driver.rs
index 1455383..186bfef 100644
--- a/crates/shirabe/src/repository/vcs/git_driver.rs
+++ b/crates/shirabe/src/repository/vcs/git_driver.rs
@@ -455,7 +455,7 @@ impl GitDriver {
url,
Some(&sys_get_temp_dir()),
false,
- None,
+ (),
) {
Ok(_) => Ok(true),
Err(e) => {
diff --git a/crates/shirabe/src/util/git.rs b/crates/shirabe/src/util/git.rs
index bd7b13b..4520759 100644
--- a/crates/shirabe/src/util/git.rs
+++ b/crates/shirabe/src/util/git.rs
@@ -17,8 +17,8 @@ use indexmap::IndexMap;
use shirabe_external_packages::composer::pcre::{CaptureKey, Preg};
use shirabe_php_shim::{
InvalidArgumentException, PHP_EOL, PhpMixed, RuntimeException, array_map, clearstatcache,
- explode, implode, in_array, is_callable, is_dir, preg_quote, rawurldecode, rawurlencode,
- str_contains, str_ends_with, str_replace_array, strlen, strpos, substr, trim, version_compare,
+ explode, implode, in_array, is_dir, preg_quote, rawurldecode, rawurlencode, str_contains,
+ str_ends_with, str_replace_array, strlen, strpos, substr, trim, version_compare,
};
use std::sync::Mutex;
@@ -104,7 +104,7 @@ impl Git {
url: &str,
cwd: Option<&str>,
initial_clone: bool,
- command_output: Option<&mut PhpMixed>,
+ command_output: impl RunCommandOutput,
) -> anyhow::Result<()> {
let mut callables: Vec<Box<dyn Fn(&str) -> Vec<String>>> = vec![];
for cmd in commands {
@@ -136,7 +136,7 @@ impl Git {
url: &str,
cwd: Option<&str>,
initial_clone: bool,
- command_output: Option<&mut PhpMixed>,
+ command_output: impl RunCommandOutput,
) -> anyhow::Result<()> {
self.run_command(command_callable, url, cwd, initial_clone, command_output)
}
@@ -150,7 +150,7 @@ impl Git {
url: &str,
cwd: Option<&str>,
initial_clone: bool,
- mut command_output: Option<&mut PhpMixed>,
+ mut command_output: impl RunCommandOutput,
) -> anyhow::Result<()> {
let command_callables = command_callable;
let mut last_command: PhpMixed = PhpMixed::String(String::new());
@@ -174,12 +174,9 @@ impl Git {
let run_commands_inline = |url_arg: &str,
this_process: &mut ProcessExecutor,
last_cmd: &mut PhpMixed,
- command_output: Option<&mut PhpMixed>|
+ output: &mut dyn RunCommandOutput|
-> i64 {
- let collect_outputs = !command_output
- .as_ref()
- .map(|v| is_callable(v))
- .unwrap_or(false);
+ let collect_outputs = output.collect_outputs();
let mut outputs: Vec<String> = vec![];
let mut status: i64 = 0;
@@ -187,23 +184,32 @@ impl Git {
let cmd = callable(url_arg);
*last_cmd =
PhpMixed::List(cmd.iter().map(|s| PhpMixed::String(s.clone())).collect());
- let mut local_output = String::new();
let exec_cwd = if initial_clone && counter == 0 {
None
} else {
cwd_string.clone()
};
- status = this_process.execute_args(&cmd, &mut local_output, exec_cwd.as_deref());
if collect_outputs {
+ let mut local_output = String::new();
+ status =
+ this_process.execute_args(&cmd, &mut local_output, exec_cwd.as_deref());
outputs.push(local_output);
+ } else {
+ status = this_process
+ .execute(
+ &cmd[..],
+ output.make_handler().unwrap(),
+ exec_cwd.as_deref(),
+ )
+ .unwrap_or(1);
}
if status != 0 {
break;
}
}
- if collect_outputs && let Some(out) = command_output {
- *out = PhpMixed::String(implode("", &outputs));
+ if collect_outputs {
+ output.write_back(implode("", &outputs));
}
status
@@ -280,7 +286,7 @@ impl Git {
&proto_url,
&mut self.process.borrow_mut(),
&mut last_command,
- command_output.as_deref_mut(),
+ &mut command_output,
) == 0
{
return Ok(());
@@ -343,7 +349,7 @@ impl Git {
url,
&mut self.process.borrow_mut(),
&mut last_command,
- command_output.as_deref_mut(),
+ &mut command_output,
)
{
let mut error_msg = self.process.borrow().get_error_output().to_string();
@@ -406,7 +412,7 @@ impl Git {
&auth_url,
&mut self.process.borrow_mut(),
&mut last_command,
- command_output.as_deref_mut(),
+ &mut command_output,
) == 0
{
return Ok(());
@@ -488,7 +494,7 @@ impl Git {
&auth_url,
&mut self.process.borrow_mut(),
&mut last_command,
- command_output.as_deref_mut(),
+ &mut command_output,
) == 0
{
return Ok(());
@@ -535,7 +541,7 @@ impl Git {
&auth_url,
&mut self.process.borrow_mut(),
&mut last_command,
- command_output.as_deref_mut(),
+ &mut command_output,
) == 0
{
return Ok(());
@@ -554,7 +560,7 @@ impl Git {
&ssh_url,
&mut self.process.borrow_mut(),
&mut last_command,
- command_output.as_deref_mut(),
+ &mut command_output,
) == 0
{
return Ok(());
@@ -641,7 +647,7 @@ impl Git {
&auth_url,
&mut self.process.borrow_mut(),
&mut last_command,
- command_output.as_deref_mut(),
+ &mut command_output,
) == 0
{
return Ok(());
@@ -734,7 +740,7 @@ impl Git {
&auth_url,
&mut self.process.borrow_mut(),
&mut last_command,
- command_output,
+ &mut command_output,
) == 0
{
self.io.borrow_mut().set_authentication(
@@ -840,7 +846,7 @@ impl Git {
vec!["git".to_string(), "gc".to_string(), "--auto".to_string()],
];
- self.run_commands(commands, url, Some(dir), false, None)?;
+ self.run_commands(commands, url, Some(dir), false, ())?;
Ok(())
})();
@@ -857,7 +863,7 @@ impl Git {
url,
Some(dir),
false,
- None,
+ (),
);
if let Err(e) = try_result {
@@ -889,7 +895,7 @@ impl Git {
url,
Some(dir),
true,
- None,
+ (),
)?;
self.run_commands(
@@ -904,7 +910,7 @@ impl Git {
url,
Some(dir),
false,
- None,
+ (),
)?;
Ok(true)
@@ -1161,7 +1167,7 @@ impl Git {
],
];
- self.run_commands(commands, url, Some(dir), false, Some(&mut output_mixed))?;
+ self.run_commands(commands, url, Some(dir), false, &mut output_mixed)?;
}
let lines = self
@@ -1368,3 +1374,94 @@ impl Git {
str_replace_array(credentials, &masked_credentials, error)
}
}
+
+/// Models the `mixed &$commandOutput` parameter of `Git::runCommand` (cf.
+/// `composer/src/Composer/Util/Git.php`). In PHP the behaviour is selected by
+/// `is_callable($commandOutput)`; here each behaviour is a distinct implementing type, mirroring
+/// `ProcessExecutor`'s [`IntoExecOutput`](crate::util::process_executor::IntoExecOutput):
+///
+/// | PHP call site | meaning | implementor | `collect_outputs` |
+/// |---|---|---|---|
+/// | `runCommands($c, ...)` (no `$commandOutput`) | accumulate then discard | `()` | `true` |
+/// | `runCommands($c, ..., $var)` by-ref | accumulate and assign back | `&mut PhpMixed` | `true` |
+/// | `runCommands($c, ..., $callable)` | drive the child through the handler | [`GitRunCommandOutputHandler`] | `false` |
+pub trait RunCommandOutput {
+ /// PHP: `$collectOutputs = !is_callable($commandOutput)`.
+ fn collect_outputs(&self) -> bool;
+
+ /// In callable mode, builds the per-command output handler passed to
+ /// `ProcessExecutor::execute`. `None` in the accumulate/discard modes.
+ fn make_handler(&self) -> Option<Box<dyn FnMut(&str, &str) -> bool>>;
+
+ /// PHP: `$commandOutput = implode('', $outputs);` — only meaningful in the accumulate-and-assign
+ /// mode; a no-op for the discard and callable variants.
+ fn write_back(&mut self, value: String);
+}
+
+/// `runCommands(..., $commandOutput)` where the caller never reads `$commandOutput` (PHP default
+/// `null`): outputs are accumulated per command but thrown away.
+impl RunCommandOutput for () {
+ fn collect_outputs(&self) -> bool {
+ true
+ }
+
+ fn make_handler(&self) -> Option<Box<dyn FnMut(&str, &str) -> bool>> {
+ None
+ }
+
+ fn write_back(&mut self, _value: String) {}
+}
+
+/// `runCommands(..., $var)` by-ref: outputs are accumulated and assigned back as a
+/// [`PhpMixed::String`].
+impl RunCommandOutput for &mut PhpMixed {
+ fn collect_outputs(&self) -> bool {
+ true
+ }
+
+ fn make_handler(&self) -> Option<Box<dyn FnMut(&str, &str) -> bool>> {
+ None
+ }
+
+ fn write_back(&mut self, value: String) {
+ **self = PhpMixed::String(value);
+ }
+}
+
+/// `runCommands(..., $callable)`: the callable is used as the output handler for every command.
+/// PHP passes the same `$commandOutput` callable to each `process->execute` call, so the handler
+/// state is shared across all commands and protocol/auth retries; we mirror that by holding the
+/// handler in an [`Rc`](std::rc::Rc)/[`RefCell`](std::cell::RefCell) and producing a fresh
+/// forwarding box per command.
+pub struct GitRunCommandOutputHandler {
+ handler: std::rc::Rc<std::cell::RefCell<dyn FnMut(&str, &str) -> bool>>,
+}
+
+impl std::fmt::Debug for GitRunCommandOutputHandler {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("GitRunCommandOutputHandler").finish()
+ }
+}
+
+impl GitRunCommandOutputHandler {
+ pub fn new(handler: impl FnMut(&str, &str) -> bool + 'static) -> Self {
+ Self {
+ handler: std::rc::Rc::new(std::cell::RefCell::new(handler)),
+ }
+ }
+}
+
+impl RunCommandOutput for GitRunCommandOutputHandler {
+ fn collect_outputs(&self) -> bool {
+ false
+ }
+
+ fn make_handler(&self) -> Option<Box<dyn FnMut(&str, &str) -> bool>> {
+ let handler = self.handler.clone();
+ Some(Box::new(move |r#type: &str, buffer: &str| {
+ (handler.borrow_mut())(r#type, buffer)
+ }))
+ }
+
+ fn write_back(&mut self, _value: String) {}
+}
diff --git a/crates/shirabe/tests/util/git_test.rs b/crates/shirabe/tests/util/git_test.rs
index 43af80a..991dc91 100644
--- a/crates/shirabe/tests/util/git_test.rs
+++ b/crates/shirabe/tests/util/git_test.rs
@@ -131,7 +131,7 @@ fn test_run_command_public_git_hub_repository_not_initial_clone_ssh() {
"https://github.com/acme/repo",
None,
true,
- None,
+ (),
)
.unwrap();
}
@@ -157,7 +157,7 @@ fn test_run_command_public_git_hub_repository_not_initial_clone_https() {
"https://github.com/acme/repo",
None,
true,
- None,
+ (),
)
.unwrap();
}
@@ -188,7 +188,7 @@ fn test_run_command_private_git_hub_repository_not_initial_clone_not_interactive
"https://github.com/acme/repo",
None,
true,
- None,
+ (),
);
let err = result.expect_err("expected a RuntimeException");
@@ -232,7 +232,7 @@ fn run_command_private_github_with_authentication(
let mut git = build_git(io, config, process);
- git.__run_command(vec![command_callable], git_url, None, true, None)
+ git.__run_command(vec![command_callable], git_url, None, true, ())
.unwrap();
}
@@ -317,7 +317,7 @@ fn run_command_private_bitbucket_with_authentication(
let mut git = build_git(io, config, process);
- git.__run_command(vec![command_callable], git_url, None, true, None)
+ git.__run_command(vec![command_callable], git_url, None, true, ())
.unwrap();
}
@@ -481,7 +481,7 @@ fn run_command_private_bitbucket_interactive_with_oauth(
let mut git = build_git(io, config, process);
git.set_http_downloader(downloader);
- git.__run_command(vec![command_callable], git_url, None, true, None)
+ git.__run_command(vec![command_callable], git_url, None, true, ())
.unwrap();
}