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
use crate::{client, Protocol};
use bstr::BString;
use quick_error::quick_error;
use std::borrow::Cow;
quick_error! {
#[derive(Debug)]
#[allow(missing_docs)]
pub enum Error {
UnsupportedSshCommand(command: String) {
display("The ssh command '{}' is not currently supported", command)
}
}
}
pub fn connect(
host: &str,
path: BString,
desired_version: crate::Protocol,
user: Option<&str>,
port: Option<u16>,
) -> Result<client::file::SpawnProcessOnDemand, Error> {
let ssh_cmd_line = std::env::var("GIT_SSH_COMMAND").unwrap_or_else(|_| "ssh".into());
let mut ssh_cmd_line = ssh_cmd_line.split(' ');
let ssh_cmd = ssh_cmd_line.next().expect("there is always a single item");
type EnvVar = (&'static str, String);
let args_and_env: Option<(Vec<Cow<'_, str>>, Vec<EnvVar>)> = match ssh_cmd {
"ssh" | "ssh.exe" => {
if desired_version != Protocol::V1 {
let mut args = vec![Cow::from("-o"), "SendEnv=GIT_PROTOCOL".into()];
if let Some(port) = port {
args.push(format!("-p={}", port).into());
}
Some((
args,
vec![("GIT_PROTOCOL", format!("version={}", desired_version as usize))],
))
} else {
None
}
}
_ => return Err(Error::UnsupportedSshCommand(ssh_cmd.into())),
};
let host = match user.as_ref() {
Some(user) => format!("{}@{}", user, host),
None => host.into(),
};
let path = git_url::expand_path::for_shell(path);
let url = git_url::Url {
scheme: git_url::Scheme::Ssh,
user: user.map(Into::into),
host: Some(host.clone()),
port,
path: path.clone(),
};
Ok(match args_and_env {
Some((args, envs)) => client::file::SpawnProcessOnDemand::new_ssh(
url,
ssh_cmd.into(),
ssh_cmd_line.map(Cow::from).chain(args).chain(Some(host.into())),
envs,
path,
desired_version,
),
None => client::file::SpawnProcessOnDemand::new_ssh(
url,
ssh_cmd.into(),
ssh_cmd_line.chain(Some(host.as_str())),
None::<(&str, String)>,
path,
desired_version,
),
})
}
#[cfg(test)]
mod tests {
use crate::{client::ssh::connect, Protocol};
use bstr::ByteSlice;
#[test]
fn connect_with_tilde_in_path() {
for (url, expected) in &[
("ssh://host.xy/~/repo", "~/repo"),
("ssh://host.xy/~username/repo", "~username/repo"),
] {
let url = git_url::parse(url.as_bytes()).expect("valid url");
let cmd = connect("host", url.path, Protocol::V1, None, None).expect("parse success");
assert_eq!(
cmd.path,
expected.as_bytes().as_bstr(),
"the path is prepared to be substituted by the remote shell"
);
}
}
}