Skip to content

Commit 4e7306e

Browse files
committed
doc: Revise and somewhat expand Prepare docs
This revises the `gix_command::Prepare` documentation, mainly for clarity but also to add some information and cover or explain some cases that were not (or not as fully) covered before. This builds on recent documentation changes, such as those in #1800. Less importantly, this also: - Wraps `Prepare` documentation comments to a more consistent width, when doing so improved unrendered readability. - Made a trace message more precise, to avoid obscuring a subtlety about the distinction between what we are looking for and what we are adding, since that might occasionally relate to the reason someone is examining trace messages.
1 parent f3dc83b commit 4e7306e

File tree

1 file changed

+80
-41
lines changed

1 file changed

+80
-41
lines changed

gix-command/src/lib.rs

+80-41
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ use std::{
1010

1111
use bstr::{BString, ByteSlice};
1212

13-
/// A structure to keep settings to use when invoking a command via [`spawn()`][Prepare::spawn()], after creating it with [`prepare()`].
13+
/// A structure to keep settings to use when invoking a command via [`spawn()`][Prepare::spawn()],
14+
/// after creating it with [`prepare()`].
1415
pub struct Prepare {
15-
/// The command to invoke (either with or without shell depending on `use_shell`.
16+
/// The command to invoke, either directly or with a shell depending on `use_shell`.
1617
pub command: OsString,
1718
/// Additional information to be passed to the spawned command.
1819
pub context: Option<Context>,
@@ -22,29 +23,29 @@ pub struct Prepare {
2223
pub stdout: std::process::Stdio,
2324
/// The way standard error is configured.
2425
pub stderr: std::process::Stdio,
25-
/// The arguments to pass to the spawned process.
26+
/// The arguments to pass to the process being spawned.
2627
pub args: Vec<OsString>,
27-
/// environment variables to set in the spawned process.
28+
/// Environment variables to set for the spawned process.
2829
pub env: Vec<(OsString, OsString)>,
2930
/// If `true`, we will use `shell_program` or `sh` to execute the `command`.
3031
pub use_shell: bool,
31-
/// If `true`, `command` is assumed to be a command or path to the program to execute, and it will be shell-quoted
32-
/// to assure it will be executed as is and without splitting across whitespace.
32+
/// If `true`, `command` is assumed to be a command or path to the program to execute, and it
33+
/// will be shell-quoted to assure it will be executed as is and without splitting across
34+
/// whitespace.
3335
pub quote_command: bool,
3436
/// The name or path to the shell program to use instead of `sh`.
3537
pub shell_program: Option<OsString>,
36-
/// If `true` (default `true` on windows and `false` everywhere else)
37-
/// we will see if it's safe to manually invoke `command` after splitting
38-
/// its arguments as a shell would do.
39-
/// Note that outside of windows, it's generally not advisable as this
40-
/// removes support for literal shell scripts with shell-builtins.
38+
/// If `true` (default `true` on Windows and `false` everywhere else) we will see if it's safe
39+
/// to manually invoke `command` after splitting its arguments as a shell would do.
4140
///
42-
/// This mimics the behaviour we see with `git` on windows, which also
43-
/// won't invoke the shell there at all.
41+
/// Note that outside of Windows, it's generally not advisable as this removes support for
42+
/// literal shell scripts with shell-builtins.
4443
///
45-
/// Only effective if `use_shell` is `true` as well, as the shell will
46-
/// be used as a fallback if it's not possible to split arguments as
47-
/// the command-line contains 'scripting'.
44+
/// This mimics the behaviour we see with `git` on Windows, which also won't invoke the shell
45+
/// there at all.
46+
///
47+
/// Only effective if `use_shell` is `true` as well, as the shell will be used as a fallback if
48+
/// it's not possible to split arguments as the command-line contains 'scripting'.
4849
pub allow_manual_arg_splitting: bool,
4950
}
5051

@@ -96,81 +97,119 @@ mod prepare {
9697

9798
/// Builder
9899
impl Prepare {
99-
/// If called, the command will be checked for characters that are typical for shell scripts, and if found will use `sh` to execute it
100-
/// or whatever is set as [`with_shell_program()`](Self::with_shell_program()).
100+
/// If called, the command will be checked for characters that are typical for shell
101+
/// scripts, and if found will use `sh` to execute it or whatever is set as
102+
/// [`with_shell_program()`](Self::with_shell_program()).
103+
///
101104
/// If the command isn't valid UTF-8, a shell will always be used.
102105
///
103-
/// 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
104-
/// already present in the command.
106+
/// If a shell is used, then arguments given here with [arg()](Self::arg) or
107+
/// [args()](Self::args) will be substituted via `"$@"` if it's not already present in the
108+
/// command.
109+
///
105110
///
106-
/// If this method is not called, commands are always executed verbatim, without the use of a shell.
111+
/// The [`command_may_be_shell_script_allow_manual_argument_splitting()`](Self::command_may_be_shell_script_allow_manual_argument_splitting())
112+
/// and [`command_may_be_shell_script_disallow_manual_argument_splitting()`](Self::command_may_be_shell_script_disallow_manual_argument_splitting())
113+
/// methods also call this method.
114+
///
115+
/// If neither this method nor [`with_shell()`](Self::with_shell()) is called, commands are
116+
/// always executed verbatim and directly, without the use of a shell.
107117
pub fn command_may_be_shell_script(mut self) -> Self {
108118
self.use_shell = self.command.to_str().map_or(true, |cmd| {
109119
cmd.as_bytes().find_byteset(b"|&;<>()$`\\\"' \t\n*?[#~=%").is_some()
110120
});
111121
self
112122
}
113123

114-
/// If called, unconditionally use a shell to execute the command and its arguments, and `sh` to execute it,
115-
/// or whatever is set as [`with_shell_program()`](Self::with_shell_program()).
124+
/// If called, unconditionally use a shell to execute the command and its arguments.
125+
///
126+
/// This uses `sh` to execute it, or whatever is set as
127+
/// [`with_shell_program()`](Self::with_shell_program()).
116128
///
117-
/// 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
118-
/// already present in the command.
129+
/// Arguments given here with [arg()](Self::arg) or [args()](Self::args) will be
130+
/// substituted via `"$@"` if it's not already present in the command.
119131
///
120-
/// If this method is not called, commands are always executed verbatim, without the use of a shell.
132+
/// If neither this method nor
133+
/// [`command_may_be_shell_script()`](Self::command_may_be_shell_script()) is called,
134+
/// commands are always executed verbatim and directly, without the use of a shell. (But
135+
/// see [`command_may_be_shell_script()`](Self::command_may_be_shell_script()) on other
136+
/// methods that call that method.)
121137
pub fn with_shell(mut self) -> Self {
122138
self.use_shell = true;
123139
self
124140
}
125141

126-
/// If [`with_shell()`](Self::with_shell()) is set, then quote the command to assure its path is left intact.
142+
/// Quote the command if it is run in a shell, so its path is left intact.
127143
///
128-
/// Note that this should not be used if the command is a script - quoting is only the right choice if it's known to be a program path.
144+
/// This is only meaningful if the command has been arranged to run in a shell, either
145+
/// unconditionally with [`with_shell()`](Self::with_shell()), or conditionally with
146+
/// [`command_may_be_shell_script()`](Self::command_may_be_shell_script()).
147+
///
148+
/// Note that this should not be used if the command is a script - quoting is only the
149+
/// right choice if it's known to be a program path.
150+
///
151+
/// Note also that this does not affect arguments passed with [arg()](Self::arg) or
152+
/// [args()](Self::args), which do not have to be quoted by the *caller* because they are
153+
/// passed as `"$@"` positional parameters (`"$1"`, `"$2"`, and so on).
129154
pub fn with_quoted_command(mut self) -> Self {
130155
self.quote_command = true;
131156
self
132157
}
133158

134-
/// 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`.
159+
/// Set the name or path to the shell `program` to use if a shell is to be used, to avoid
160+
/// using the default shell which is `sh`.
161+
///
162+
/// Note that that shells that are not Bourne-style cannot be expected to work correctly,
163+
/// because POSIX shell syntax is assumed when searching for and conditionally adding
164+
/// `"$@"` to receive arguments, where applicable (and in the behaviour of
165+
/// [`with_quoted_command()`](Self::with_quoted_command()), if called).
135166
pub fn with_shell_program(mut self, program: impl Into<OsString>) -> Self {
136167
self.shell_program = Some(program.into());
137168
self
138169
}
139170

140171
/// Unconditionally turn off using the shell when spawning the command.
141-
/// Note that not using the shell is the default so an effective use of this method
142-
/// is some time after [`command_may_be_shell_script()`](Prepare::command_may_be_shell_script()) was called.
172+
///
173+
/// Note that not using the shell is the default. So an effective use of this method
174+
/// is some time after [`command_may_be_shell_script()`](Self::command_may_be_shell_script())
175+
/// or [`with_shell()`](Self::with_shell()) was called.
143176
pub fn without_shell(mut self) -> Self {
144177
self.use_shell = false;
145178
self
146179
}
147180

148181
/// Set additional `ctx` to be used when spawning the process.
149182
///
150-
/// Note that this is a must for most kind of commands that `git` usually spawns,
151-
/// as at least they need to know the correct `git` repository to function.
183+
/// Note that this is a must for most kind of commands that `git` usually spawns, as at
184+
/// least they need to know the correct Git repository to function.
152185
pub fn with_context(mut self, ctx: Context) -> Self {
153186
self.context = Some(ctx);
154187
self
155188
}
156189

157-
/// 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.
190+
/// Like [`command_may_be_shell_script()`](Self::command_may_be_shell_script()), but try to
191+
/// split arguments by hand if this can be safely done without a shell.
192+
///
193+
/// This is useful on platforms where spawning processes is slow, or where many processes
194+
/// have to be spawned in a row which should be sped up. Manual argument splitting is
195+
/// enabled by default on Windows only.
158196
///
159-
/// 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.
160-
/// Manual argument splitting is enabled by default on Windows only.
197+
/// Note that this does *not* check for the use of possible shell builtins. Commands may
198+
/// fail or behave differently if they are available as shell builtins and no corresponding
199+
/// external command exists, or the external command behaves differently.
161200
pub fn command_may_be_shell_script_allow_manual_argument_splitting(mut self) -> Self {
162201
self.allow_manual_arg_splitting = true;
163202
self.command_may_be_shell_script()
164203
}
165204

166-
/// 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
167-
/// can be performed safely.
205+
/// Like [`command_may_be_shell_script()`](Self::command_may_be_shell_script()), but don't
206+
/// allow to bypass the shell even if manual argument splitting can be performed safely.
168207
pub fn command_may_be_shell_script_disallow_manual_argument_splitting(mut self) -> Self {
169208
self.allow_manual_arg_splitting = false;
170209
self.command_may_be_shell_script()
171210
}
172211

173-
/// Configure the process to use `stdio` for _stdin.
212+
/// Configure the process to use `stdio` for _stdin_.
174213
pub fn stdin(mut self, stdio: Stdio) -> Self {
175214
self.stdin = stdio;
176215
self
@@ -180,7 +219,7 @@ mod prepare {
180219
self.stdout = stdio;
181220
self
182221
}
183-
/// Configure the process to use `stdio` for _stderr.
222+
/// Configure the process to use `stdio` for _stderr_.
184223
pub fn stderr(mut self, stdio: Stdio) -> Self {
185224
self.stderr = stdio;
186225
self
@@ -256,7 +295,7 @@ mod prepare {
256295
prep.command.push(" \"$@\"");
257296
} else {
258297
gix_trace::debug!(
259-
"Will not add '$@' to '{:?}' as it seems to contain it already",
298+
"Will not add '\"$@\"' to '{:?}' as it seems to contain '$@' already",
260299
prep.command
261300
);
262301
}

0 commit comments

Comments
 (0)