Skip to content

Commit

Permalink
Merge branch 'adjustments-for-cargo'
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Dec 29, 2022
2 parents 9fabfc5 + d48b9a7 commit f8c562a
Show file tree
Hide file tree
Showing 34 changed files with 1,252 additions and 437 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ git-features = { version = "^0.25.1", path = "git-features" }
git-repository = { version = "^0.30.2", path = "git-repository", default-features = false }

clap = { version = "3.2.5", features = ["derive", "cargo"] }
prodash = { version = "22.0", optional = true, default-features = false }
prodash = { version = "23.0", optional = true, default-features = false }
is-terminal = { version = "0.4.0", optional = true }
env_logger = { version = "0.10.0", default-features = false }
crosstermion = { version = "0.10.1", optional = true, default-features = false }
Expand Down
4 changes: 2 additions & 2 deletions etc/check-package-size.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ echo "in root: gitoxide CLI"
(enter git-lock && indent cargo diet -n --package-size-limit 20KB)
(enter git-config && indent cargo diet -n --package-size-limit 120KB)
(enter git-config-value && indent cargo diet -n --package-size-limit 20KB)
(enter git-command && indent cargo diet -n --package-size-limit 5KB)
(enter git-command && indent cargo diet -n --package-size-limit 10KB)
(enter git-hash && indent cargo diet -n --package-size-limit 30KB)
(enter git-chunk && indent cargo diet -n --package-size-limit 10KB)
(enter git-rebase && indent cargo diet -n --package-size-limit 5KB)
Expand Down Expand Up @@ -59,5 +59,5 @@ echo "in root: gitoxide CLI"
(enter git-protocol && indent cargo diet -n --package-size-limit 80KB)
(enter git-packetline && indent cargo diet -n --package-size-limit 35KB)
(enter git-repository && indent cargo diet -n --package-size-limit 250KB)
(enter git-transport && indent cargo diet -n --package-size-limit 75KB)
(enter git-transport && indent cargo diet -n --package-size-limit 80KB)
(enter gitoxide-core && indent cargo diet -n --package-size-limit 100KB)
49 changes: 40 additions & 9 deletions git-command/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@ use std::ffi::OsString;

/// A structure to keep settings to use when invoking a command via [`spawn()`][Prepare::spawn()], after creating it with [`prepare()`].
pub struct Prepare {
command: OsString,
stdin: std::process::Stdio,
stdout: std::process::Stdio,
stderr: std::process::Stdio,
args: Vec<OsString>,
use_shell: bool,
/// The command to invoke (either with or without shell depending on `use_shell`.
pub command: OsString,
/// The way standard input is configured.
pub stdin: std::process::Stdio,
/// The way standard output is configured.
pub stdout: std::process::Stdio,
/// The way standard error is configured.
pub stderr: std::process::Stdio,
/// The arguments to pass to the spawned process.
pub args: Vec<OsString>,
/// environment variables to set in the spawned process.
pub env: Vec<(OsString, OsString)>,
/// If `true`, we will use `sh` to execute the `command`.
pub use_shell: bool,
}

mod prepare {
use std::ffi::OsString;
use std::process::{Command, Stdio};

use bstr::ByteSlice;
Expand All @@ -34,6 +43,14 @@ mod prepare {
self
}

/// Unconditionally turn off using the shell when spawning the command.
/// Note that not using the shell is the default so an effective use of this method
/// is some time after [`with_shell()`][Prepare::with_shell()] was called.
pub fn without_shell(mut self) -> Self {
self.use_shell = false;
self
}

/// Configure the process to use `stdio` for _stdin.
pub fn stdin(mut self, stdio: Stdio) -> Self {
self.stdin = stdio;
Expand All @@ -51,18 +68,30 @@ mod prepare {
}

/// Add `arg` to the list of arguments to call the command with.
pub fn arg(mut self, arg: impl Into<std::ffi::OsString>) -> Self {
pub fn arg(mut self, arg: impl Into<OsString>) -> Self {
self.args.push(arg.into());
self
}

/// Add `args` to the list of arguments to call the command with.
pub fn args(mut self, args: impl IntoIterator<Item = impl Into<OsString>>) -> Self {
self.args
.append(&mut args.into_iter().map(Into::into).collect::<Vec<_>>());
self
}

/// Add `key` with `value` to the environment of the spawned command.
pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Self {
self.env.push((key.into(), value.into()));
self
}
}

/// Finalization
impl Prepare {
/// Spawn the command as configured.
pub fn spawn(self) -> std::io::Result<std::process::Child> {
let mut cmd: Command = self.into();
cmd.spawn()
Command::from(self).spawn()
}
}

Expand All @@ -83,6 +112,7 @@ mod prepare {
cmd.stdin(prep.stdin)
.stdout(prep.stdout)
.stderr(prep.stderr)
.envs(prep.env)
.args(prep.args);
cmd
}
Expand All @@ -103,6 +133,7 @@ pub fn prepare(cmd: impl Into<OsString>) -> Prepare {
stdout: std::process::Stdio::piped(),
stderr: std::process::Stdio::inherit(),
args: Vec::new(),
env: Vec::new(),
use_shell: false,
}
}
45 changes: 45 additions & 0 deletions git-command/tests/command.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,51 @@
use git_testtools::Result;

mod prepare {
fn quoted(input: &[&str]) -> String {
input.iter().map(|s| format!("\"{s}\"")).collect::<Vec<_>>().join(" ")
}
#[test]
fn single_and_multiple_arguments() {
let cmd = std::process::Command::from(git_command::prepare("ls").arg("first").args(["second", "third"]));
assert_eq!(format!("{cmd:?}"), quoted(&["ls", "first", "second", "third"]));
}
}

mod spawn {
use bstr::ByteSlice;

#[test]
#[cfg(unix)]
fn environment_variables_are_passed_one_by_one() -> crate::Result {
let out = git_command::prepare("echo $FIRST $SECOND")
.env("FIRST", "first")
.env("SECOND", "second")
.with_shell()
.spawn()?
.wait_with_output()?;
assert_eq!(out.stdout.as_bstr(), "first second\n");
Ok(())
}

#[test]
#[cfg(unix)]
fn disallow_shell() -> crate::Result {
let out = git_command::prepare("echo hi")
.with_shell()
.spawn()?
.wait_with_output()?;
assert_eq!(out.stdout.as_bstr(), "hi\n");
assert!(
git_command::prepare("echo hi")
.with_shell()
.without_shell()
.spawn()
.is_err(),
"no command named 'echo hi' exists"
);
Ok(())
}

#[test]
fn direct_command_execution_searches_in_path() -> crate::Result {
assert!(git_command::prepare(if cfg!(unix) { "ls" } else { "dir.exe" })
Expand Down
18 changes: 17 additions & 1 deletion git-credentials/src/helper/cascade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ impl Default for Cascade {
programs: Vec::new(),
stderr: true,
use_http_path: false,
query_user_only: false,
}
}
}
Expand Down Expand Up @@ -50,6 +51,14 @@ impl Cascade {
self.use_http_path = toggle;
self
}

/// If `toggle` is true, a bogus password will be provided to prevent any helper program from prompting for it, nor will
/// we prompt for the password. The resulting identity will have a bogus password and it's expected to not be used by the
/// consuming transport.
pub fn query_user_only(mut self, toggle: bool) -> Self {
self.query_user_only = toggle;
self
}
}

/// Finalize
Expand All @@ -63,7 +72,14 @@ impl Cascade {
pub fn invoke(&mut self, mut action: helper::Action, mut prompt: git_prompt::Options<'_>) -> protocol::Result {
let mut url = action
.context_mut()
.map(|ctx| ctx.destructure_url_in_place(self.use_http_path))
.map(|ctx| {
ctx.destructure_url_in_place(self.use_http_path).map(|ctx| {
if self.query_user_only && ctx.password.is_none() {
ctx.password = Some("".into());
}
ctx
})
})
.transpose()?
.and_then(|ctx| ctx.url.take());

Expand Down
6 changes: 5 additions & 1 deletion git-credentials/src/helper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ pub struct Cascade {
/// If true, stderr is enabled when `programs` are run, which is the default.
pub stderr: bool,
/// If true, http(s) urls will take their path portion into account when obtaining credentials. Default is false.
/// Other protocols like ssh will always use the path portion.
pub use_http_path: bool,
/// If true, default false, when getting credentials, we will set a bogus password to only obtain the user name.
/// Storage and cancellation work the same, but without a password set.
pub query_user_only: bool,
}

/// The outcome of the credentials helper [invocation][crate::helper::invoke()].
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Outcome {
/// The username to use in the identity, if set.
pub username: Option<String>,
/// The username to use in the identity, if set.
/// The password to use in the identity, if set.
pub password: Option<String>,
/// If set, the helper asked to stop the entire process, whether the identity is complete or not.
pub quit: bool,
Expand Down
33 changes: 29 additions & 4 deletions git-credentials/tests/helper/cascade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,26 @@ mod invoke {

#[test]
fn credentials_are_filled_in_one_by_one_and_stop_when_complete() {
let actual = invoke_cascade(["username", "password", "custom-helper"], action_get())
.unwrap()
.expect("credentials");
assert_eq!(actual.identity, identity("user", "pass"));
}

#[test]
fn usernames_in_urls_are_kept_if_the_helper_does_not_overwrite_it() {
let actual = invoke_cascade(
["username", "password", "custom-helper"],
Action::get_for_url("does/not/matter"),
["password", "custom-helper"],
Action::get_for_url("ssh://git@host.org/path"),
)
.unwrap()
.expect("credentials");
assert_eq!(actual.identity, identity("user", "pass"));
assert_eq!(actual.identity, identity("git", "pass"));
}

#[test]
fn partial_credentials_can_be_overwritten_by_complete_ones() {
let actual = invoke_cascade(["username", "custom-helper"], Action::get_for_url("does/not/matter"))
let actual = invoke_cascade(["username", "custom-helper"], action_get())
.unwrap()
.expect("credentials");
assert_eq!(actual.identity, identity("user-script", "pass-script"));
Expand Down Expand Up @@ -113,6 +121,23 @@ mod invoke {
assert_eq!(actual.identity, identity("user", "pass"));
}

#[test]
fn bogus_password_overrides_any_helper_and_helper_overrides_username_in_url() {
let actual = Cascade::default()
.query_user_only(true)
.extend(fixtures(["username", "password"]))
.invoke(
Action::get_for_url("ssh://git@host/repo"),
git_prompt::Options {
mode: git_prompt::Mode::Disable,
askpass: None,
},
)
.unwrap()
.expect("credentials");
assert_eq!(actual.identity, identity("user", ""));
}

fn action_get() -> Action {
Action::get_for_url("does/not/matter")
}
Expand Down
2 changes: 1 addition & 1 deletion git-features/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ crc32fast = { version = "1.2.1", optional = true }
sha1 = { version = "0.10.0", optional = true }

# progress
prodash = { version = "22.0", optional = true, default-features = false, features = ["unit-bytes", "unit-human"] }
prodash = { version = "23.0", optional = true, default-features = false, features = ["unit-bytes", "unit-human"] }

# pipe
bytes = { version = "1.0.0", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion git-repository/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ git-index = { version = "^0.10.0", path = "../git-index" }
git-worktree = { version = "^0.10.0", path = "../git-worktree" }
git-hashtable = { version = "^0.1.0", path = "../git-hashtable" }

prodash = { version = "22.0.0", default-features = false, features = ["progress-tree"] }
prodash = { version = "23.0.0", default-features = false, features = ["progress-tree"] }
once_cell = "1.14.0"
signal-hook = { version = "0.3.9", default-features = false }
thiserror = "1.0.26"
Expand Down

0 comments on commit f8c562a

Please sign in to comment.