1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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;