Skip to content

Commit cc7b614

Browse files
authored
Merge pull request #1800 from GitoxideLabs/improvements
various improvements
2 parents 7c803fa + 28f712e commit cc7b614

File tree

8 files changed

+92
-40
lines changed

8 files changed

+92
-40
lines changed

gix-command/src/lib.rs

+30-14
Original file line numberDiff line numberDiff line change
@@ -93,27 +93,41 @@ mod prepare {
9393

9494
/// Builder
9595
impl Prepare {
96-
/// If called, the command will not be executed directly, but with `sh`, but only if the
97-
/// command passed to [`prepare`](super::prepare()) requires this.
96+
/// If called, the command will be checked for characters that are typical for shell scripts, and if found will use `sh` to execute it
97+
/// or whatever is set as [`with_shell_program()`](Self::with_shell_program()).
98+
/// If the command isn't valid UTF-8, a shell will always be used.
9899
///
99-
/// This also allows to pass shell scripts as command, or use commands that contain arguments which are subsequently
100-
/// parsed by `sh`.
101-
pub fn with_shell(mut self) -> Self {
100+
/// If a shell is used, then arguments given here with [arg()](Self::arg) or [args()](Self::args) will be substituted via `"$@"` if it's not
101+
/// already present in the command.
102+
///
103+
/// If this method is not called, commands are always executed verbatim, without the use of a shell.
104+
pub fn command_may_be_shell_script(mut self) -> Self {
102105
self.use_shell = self.command.to_str().map_or(true, |cmd| {
103106
cmd.as_bytes().find_byteset(b"|&;<>()$`\\\"' \t\n*?[#~=%").is_some()
104107
});
105108
self
106109
}
107110

108-
/// Set the name or path to the shell `program` to use, to avoid using the default shell which is `sh`.
111+
/// If called, unconditionally use a shell to execute the command and its arguments, and `sh` to execute it,
112+
/// or whatever is set as [`with_shell_program()`](Self::with_shell_program()).
113+
///
114+
/// If a shell is used, then arguments given here with [arg()](Self::arg) or [args()](Self::args) will be substituted via `"$@"` if it's not
115+
/// already present in the command.
116+
///
117+
/// If this method is not called, commands are always executed verbatim, without the use of a shell.
118+
pub fn with_shell(mut self) -> Self {
119+
self.use_shell = true;
120+
self
121+
}
122+
/// Set the name or path to the shell `program` to use if a shell is to be used, to avoid using the default shell which is `sh`.
109123
pub fn with_shell_program(mut self, program: impl Into<OsString>) -> Self {
110124
self.shell_program = Some(program.into());
111125
self
112126
}
113127

114128
/// Unconditionally turn off using the shell when spawning the command.
115129
/// Note that not using the shell is the default so an effective use of this method
116-
/// is some time after [`with_shell()`][Prepare::with_shell()] was called.
130+
/// is some time after [`command_may_be_shell_script()`](Prepare::command_may_be_shell_script()) was called.
117131
pub fn without_shell(mut self) -> Self {
118132
self.use_shell = false;
119133
self
@@ -128,18 +142,20 @@ mod prepare {
128142
self
129143
}
130144

131-
/// Use a shell, but try to split arguments by hand if this can be safely done without a shell.
145+
/// Like [`command_may_be_shell_script()`](Prepare::command_may_be_shell_script()), but try to split arguments by hand if this can be safely done without a shell.
132146
///
133-
/// If that's not the case, use a shell instead.
134-
pub fn with_shell_allow_manual_argument_splitting(mut self) -> Self {
147+
/// This is useful on platforms where spawning processes is slow, or where many processes have to be spawned in a raw which should be sped up.
148+
/// Manual argument splitting is enabled by default on Windows only.
149+
pub fn command_may_be_shell_script_allow_manual_argument_splitting(mut self) -> Self {
135150
self.allow_manual_arg_splitting = true;
136-
self.with_shell()
151+
self.command_may_be_shell_script()
137152
}
138153

139-
/// Use a shell, but prohibit splitting arguments by hand even if this could be safely done without a shell.
140-
pub fn with_shell_disallow_manual_argument_splitting(mut self) -> Self {
154+
/// Like [`command_may_be_shell_script()`](Prepare::command_may_be_shell_script()), but don't allow to bypass the shell even if manual argument splitting
155+
/// can be performed safely.
156+
pub fn command_may_be_shell_script_disallow_manual_argument_splitting(mut self) -> Self {
141157
self.allow_manual_arg_splitting = false;
142-
self.with_shell()
158+
self.command_may_be_shell_script()
143159
}
144160

145161
/// Configure the process to use `stdio` for _stdin.

gix-command/tests/command.rs

+55-19
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ mod prepare {
246246
#[test]
247247
fn multiple_arguments_in_one_line_with_auto_split() {
248248
let cmd = std::process::Command::from(
249-
gix_command::prepare("echo first second third").with_shell_allow_manual_argument_splitting(),
249+
gix_command::prepare("echo first second third")
250+
.command_may_be_shell_script_allow_manual_argument_splitting(),
250251
);
251252
assert_eq!(
252253
format!("{cmd:?}"),
@@ -267,7 +268,8 @@ mod prepare {
267268

268269
#[test]
269270
fn single_and_multiple_arguments_as_part_of_command_with_shell() {
270-
let cmd = std::process::Command::from(gix_command::prepare("ls first second third").with_shell());
271+
let cmd =
272+
std::process::Command::from(gix_command::prepare("ls first second third").command_may_be_shell_script());
271273
assert_eq!(
272274
format!("{cmd:?}"),
273275
if cfg!(windows) {
@@ -283,7 +285,7 @@ mod prepare {
283285
fn single_and_multiple_arguments_as_part_of_command_with_given_shell() {
284286
let cmd = std::process::Command::from(
285287
gix_command::prepare("ls first second third")
286-
.with_shell()
288+
.command_may_be_shell_script()
287289
.with_shell_program("/somepath/to/bash"),
288290
);
289291
assert_eq!(
@@ -299,7 +301,11 @@ mod prepare {
299301

300302
#[test]
301303
fn single_and_complex_arguments_as_part_of_command_with_shell() {
302-
let cmd = std::process::Command::from(gix_command::prepare("ls --foo \"a b\"").arg("additional").with_shell());
304+
let cmd = std::process::Command::from(
305+
gix_command::prepare("ls --foo \"a b\"")
306+
.arg("additional")
307+
.command_may_be_shell_script(),
308+
);
303309
assert_eq!(
304310
format!("{cmd:?}"),
305311
if cfg!(windows) {
@@ -314,7 +320,7 @@ mod prepare {
314320
#[test]
315321
fn single_and_complex_arguments_with_auto_split() {
316322
let cmd = std::process::Command::from(
317-
gix_command::prepare("ls --foo=\"a b\"").with_shell_allow_manual_argument_splitting(),
323+
gix_command::prepare("ls --foo=\"a b\"").command_may_be_shell_script_allow_manual_argument_splitting(),
318324
);
319325
assert_eq!(
320326
format!("{cmd:?}"),
@@ -326,15 +332,29 @@ mod prepare {
326332
#[test]
327333
fn single_and_complex_arguments_without_auto_split() {
328334
let cmd = std::process::Command::from(
329-
gix_command::prepare("ls --foo=\"a b\"").with_shell_disallow_manual_argument_splitting(),
335+
gix_command::prepare("ls --foo=\"a b\"").command_may_be_shell_script_disallow_manual_argument_splitting(),
330336
);
331337
assert_eq!(format!("{cmd:?}"), quoted(&[SH, "-c", r#"ls --foo=\"a b\""#, "--"]));
332338
}
333339

340+
#[test]
341+
fn single_and_simple_arguments_without_auto_split_with_shell() {
342+
let cmd = std::process::Command::from(
343+
gix_command::prepare("ls")
344+
.arg("--foo=a b")
345+
.command_may_be_shell_script_disallow_manual_argument_splitting()
346+
.with_shell(),
347+
);
348+
assert_eq!(
349+
format!("{cmd:?}"),
350+
quoted(&[SH, "-c", "ls \\\"$@\\\"", "--", "--foo=a b"])
351+
);
352+
}
353+
334354
#[test]
335355
fn single_and_complex_arguments_will_not_auto_split_on_special_characters() {
336356
let cmd = std::process::Command::from(
337-
gix_command::prepare("ls --foo=~/path").with_shell_allow_manual_argument_splitting(),
357+
gix_command::prepare("ls --foo=~/path").command_may_be_shell_script_allow_manual_argument_splitting(),
338358
);
339359
assert_eq!(
340360
format!("{cmd:?}"),
@@ -345,7 +365,8 @@ mod prepare {
345365

346366
#[test]
347367
fn tilde_path_and_multiple_arguments_as_part_of_command_with_shell() {
348-
let cmd = std::process::Command::from(gix_command::prepare("~/bin/exe --foo \"a b\"").with_shell());
368+
let cmd =
369+
std::process::Command::from(gix_command::prepare("~/bin/exe --foo \"a b\"").command_may_be_shell_script());
349370
assert_eq!(
350371
format!("{cmd:?}"),
351372
format!(r#""{SH}" "-c" "~/bin/exe --foo \"a b\"" "--""#),
@@ -355,7 +376,11 @@ mod prepare {
355376

356377
#[test]
357378
fn script_with_dollar_at() {
358-
let cmd = std::process::Command::from(gix_command::prepare("echo \"$@\" >&2").with_shell().arg("store"));
379+
let cmd = std::process::Command::from(
380+
gix_command::prepare("echo \"$@\" >&2")
381+
.command_may_be_shell_script()
382+
.arg("store"),
383+
);
359384
assert_eq!(
360385
format!("{cmd:?}"),
361386
format!(r#""{SH}" "-c" "echo \"$@\" >&2" "--" "store""#),
@@ -374,7 +399,7 @@ mod spawn {
374399
let out = gix_command::prepare("echo $FIRST $SECOND")
375400
.env("FIRST", "first")
376401
.env("SECOND", "second")
377-
.with_shell()
402+
.command_may_be_shell_script()
378403
.spawn()?
379404
.wait_with_output()?;
380405
assert_eq!(out.stdout.as_bstr(), "first second\n");
@@ -385,12 +410,15 @@ mod spawn {
385410
#[cfg(unix)]
386411
fn disallow_shell() -> crate::Result {
387412
let out = gix_command::prepare("PATH= echo hi")
388-
.with_shell()
413+
.command_may_be_shell_script()
389414
.spawn()?
390415
.wait_with_output()?;
391416
assert_eq!(out.stdout.as_bstr(), "hi\n");
392417

393-
let mut cmd: std::process::Command = gix_command::prepare("echo hi").with_shell().without_shell().into();
418+
let mut cmd: std::process::Command = gix_command::prepare("echo hi")
419+
.command_may_be_shell_script()
420+
.without_shell()
421+
.into();
394422
assert!(
395423
cmd.env_remove("PATH").spawn().is_err(),
396424
"no command named 'echo hi' exists"
@@ -400,9 +428,13 @@ mod spawn {
400428

401429
#[test]
402430
fn script_with_dollar_at() -> crate::Result {
403-
let out = std::process::Command::from(gix_command::prepare("echo \"$@\"").with_shell().arg("arg"))
404-
.spawn()?
405-
.wait_with_output()?;
431+
let out = std::process::Command::from(
432+
gix_command::prepare("echo \"$@\"")
433+
.command_may_be_shell_script()
434+
.arg("arg"),
435+
)
436+
.spawn()?
437+
.wait_with_output()?;
406438
assert_eq!(
407439
out.stdout.to_str_lossy().trim(),
408440
"arg",
@@ -433,7 +465,7 @@ mod spawn {
433465
#[test]
434466
fn command_in_path_with_args() -> crate::Result {
435467
assert!(gix_command::prepare(if cfg!(unix) { "ls -l" } else { "dir.exe -a" })
436-
.with_shell()
468+
.command_may_be_shell_script()
437469
.spawn()?
438470
.wait()?
439471
.success());
@@ -442,14 +474,18 @@ mod spawn {
442474

443475
#[test]
444476
fn sh_shell_specific_script_code() -> crate::Result {
445-
assert!(gix_command::prepare(":;:;:").with_shell().spawn()?.wait()?.success());
477+
assert!(gix_command::prepare(":;:;:")
478+
.command_may_be_shell_script()
479+
.spawn()?
480+
.wait()?
481+
.success());
446482
Ok(())
447483
}
448484

449485
#[test]
450486
fn sh_shell_specific_script_code_with_single_extra_arg() -> crate::Result {
451487
let out = gix_command::prepare("printf")
452-
.with_shell()
488+
.command_may_be_shell_script()
453489
.arg("1")
454490
.spawn()?
455491
.wait_with_output()?;
@@ -461,7 +497,7 @@ mod spawn {
461497
#[test]
462498
fn sh_shell_specific_script_code_with_multiple_extra_args() -> crate::Result {
463499
let out = gix_command::prepare("printf")
464-
.with_shell()
500+
.command_may_be_shell_script()
465501
.arg("%s")
466502
.arg("arg")
467503
.spawn()?

gix-credentials/src/program/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,14 @@ impl Program {
8282
args.insert_str(0, git_program.to_string_lossy().as_ref());
8383
gix_command::prepare(gix_path::from_bstr(args.as_bstr()).into_owned())
8484
.arg(action.as_arg(true))
85-
.with_shell_allow_manual_argument_splitting()
85+
.command_may_be_shell_script_allow_manual_argument_splitting()
8686
.into()
8787
}
8888
Kind::ExternalShellScript(for_shell)
8989
| Kind::ExternalPath {
9090
path_and_args: for_shell,
9191
} => gix_command::prepare(gix_path::from_bstr(for_shell.as_bstr()).as_ref())
92-
.with_shell()
92+
.command_may_be_shell_script()
9393
.arg(action.as_arg(true))
9494
.into(),
9595
};

gix-diff/src/blob/pipeline.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ impl Driver {
543543
let cmd = gix_command::prepare(gix_path::from_bstr(command).into_owned())
544544
// TODO: Add support for an actual Context, validate it *can* match Git
545545
.with_context(Default::default())
546-
.with_shell()
546+
.command_may_be_shell_script()
547547
.stdin(Stdio::null())
548548
.stdout(Stdio::piped())
549549
.stderr(Stdio::piped())

gix-filter/src/driver/init.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ fn spawn_driver(
9292
context: &gix_command::Context,
9393
) -> Result<(std::process::Child, std::process::Command), Error> {
9494
let mut cmd: std::process::Command = gix_command::prepare(gix_path::from_bstr(cmd).into_owned())
95-
.with_shell()
95+
.command_may_be_shell_script()
9696
.with_context(context.clone())
9797
.stdin(Stdio::piped())
9898
.stdout(Stdio::piped())

gix-merge/src/blob/platform/merge.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ pub(super) mod inner {
214214
Ok(merge::Command {
215215
cmd: gix_command::prepare(gix_path::from_bstring(cmd))
216216
.with_context(context)
217-
.with_shell()
217+
.command_may_be_shell_script()
218218
.stdin(Stdio::null())
219219
.stdout(Stdio::inherit())
220220
.stderr(Stdio::inherit())

gix-transport/src/client/blocking_io/ssh/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub fn connect(
116116
.stderr(Stdio::null())
117117
.stdout(Stdio::null())
118118
.stdin(Stdio::null())
119-
.with_shell()
119+
.command_may_be_shell_script()
120120
.arg("-G")
121121
.arg(match url.host_as_argument() {
122122
Usable(host) => host,

gix-transport/src/client/blocking_io/ssh/program_kind.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ impl ProgramKind {
2929
desired_version: Protocol,
3030
disallow_shell: bool,
3131
) -> Result<gix_command::Prepare, ssh::invocation::Error> {
32-
let mut prepare = gix_command::prepare(ssh_cmd).with_shell();
32+
let mut prepare = gix_command::prepare(ssh_cmd).command_may_be_shell_script();
3333
if disallow_shell {
3434
prepare.use_shell = false;
3535
}

0 commit comments

Comments
 (0)