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
use crate::{client, Protocol}; use bstr::BString; use quick_error::quick_error; use std::borrow::Cow; quick_error! { #[derive(Debug)] pub enum Error { UnsupportedSshCommand(command: String) { display("The ssh command '{}' is not currently supported", command) } } } pub fn connect( host: &str, path: BString, 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 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={}", 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, version, ), None => client::file::SpawnProcessOnDemand::new_ssh( url, ssh_cmd.into(), ssh_cmd_line.chain(Some(host.as_str())), None::<(&str, String)>, path, 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" ); } } }