use std::process::Stdio;
use crate::{client::blocking_io, Protocol};
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("The scheme in \"{}\" is not usable for an ssh connection", .0.to_bstring())]
UnsupportedScheme(git_url::Url),
}
impl crate::IsSpuriousError for Error {}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ProgramKind {
Ssh,
Plink,
Putty,
TortoisePlink,
Simple,
}
mod program_kind;
pub mod invocation {
use std::ffi::OsString;
#[derive(Debug, thiserror::Error)]
#[error("The 'Simple' ssh variant doesn't support {function}")]
pub struct Error {
pub command: OsString,
pub function: &'static str,
}
}
pub mod connect {
use std::ffi::{OsStr, OsString};
use crate::client::ssh::ProgramKind;
#[derive(Debug, Clone, Default)]
pub struct Options {
pub command: Option<OsString>,
pub disallow_shell: bool,
pub kind: Option<ProgramKind>,
}
impl Options {
pub fn ssh_command(&self) -> &OsStr {
self.command
.as_deref()
.or_else(|| self.kind.and_then(|kind| kind.exe()))
.unwrap_or_else(|| OsStr::new("ssh"))
}
}
}
pub fn connect(
url: git_url::Url,
desired_version: Protocol,
options: connect::Options,
) -> Result<blocking_io::file::SpawnProcessOnDemand, Error> {
if url.scheme != git_url::Scheme::Ssh || url.host().is_none() {
return Err(Error::UnsupportedScheme(url));
}
let ssh_cmd = options.ssh_command();
let mut kind = options.kind.unwrap_or_else(|| ProgramKind::from(ssh_cmd));
if options.kind.is_none() && kind == ProgramKind::Simple {
kind = if std::process::Command::from(
git_command::prepare(ssh_cmd)
.stderr(Stdio::null())
.stdout(Stdio::null())
.stdin(Stdio::null())
.with_shell()
.arg("-G")
.arg(url.host().expect("always set for ssh urls")),
)
.status()
.ok()
.map_or(false, |status| status.success())
{
ProgramKind::Ssh
} else {
ProgramKind::Simple
};
}
let path = git_url::expand_path::for_shell(url.path.clone());
Ok(blocking_io::file::SpawnProcessOnDemand::new_ssh(
url,
ssh_cmd,
path,
kind,
options.disallow_shell,
desired_version,
))
}
#[cfg(test)]
mod tests;