Skip to content

Commit

Permalink
ssh: implement Git SSH variant selection
Browse files Browse the repository at this point in the history
We'd implemented Git's autodetection of SSH variants in the past, but we
hadn't implemented the newer explicit SSH selection.  Since we're about
to pass some additional options to our ssh binaries, let's implement the
proper variant using GIT_SSH_VARIANT and ssh.variant.

Roughly, the algorithm is this: GIT_SSH_VARIANT overrides ssh.variant,
and if neither is set, autodetection occurs.  A user can specify either
"ssh" (OpenSSH), "putty" (or its synonym "plink"), "tortoiseplink", or
"simple", or, if they'd like the autodetection behavior, "auto".  If the
value is not any of these, then it is interpreted as "ssh".
  • Loading branch information
bk2204 committed Mar 31, 2021
1 parent 4e9391c commit 9114a57
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 14 deletions.
74 changes: 60 additions & 14 deletions ssh/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ import (
"github.com/rubyist/tracerx"
)

type sshVariant string

const (
variantSSH = sshVariant("ssh")
variantSimple = sshVariant("simple")
variantPutty = sshVariant("putty")
variantTortoise = sshVariant("tortoiseplink")
)

type SSHMetadata struct {
UserAndHost string
Port string
Expand Down Expand Up @@ -47,14 +56,59 @@ func parseShellCommand(command string, existing string) (ssh string, cmd string,
return
}

func findVariant(variant string) (bool, sshVariant) {
switch variant {
case "ssh", "simple", "putty", "tortoiseplink":
return false, sshVariant(variant)
case "plink":
return false, variantPutty
case "auto":
return true, ""
default:
return false, variantSSH
}
}

func autodetectVariant(osEnv config.Environment, gitEnv config.Environment, basessh string) sshVariant {
if basessh != defaultSSHCmd {
// Strip extension for easier comparison
if ext := filepath.Ext(basessh); len(ext) > 0 {
basessh = basessh[:len(basessh)-len(ext)]
}
if strings.EqualFold(basessh, "plink") {
return variantPutty
}
if strings.EqualFold(basessh, "tortoiseplink") {
return variantTortoise
}
}
return "ssh"
}

func getVariant(osEnv config.Environment, gitEnv config.Environment, basessh string) sshVariant {
variant, ok := osEnv.Get("GIT_SSH_VARIANT")
autodetect, val := findVariant(variant)
if ok {
if autodetect {
return autodetectVariant(osEnv, gitEnv, basessh)
}
return val
}
variant, ok = gitEnv.Get("GIT_SSH_VARIANT")
if autodetect, val := findVariant(variant); ok {
if autodetect {
return autodetectVariant(osEnv, gitEnv, basessh)
}
return val
}
return autodetectVariant(osEnv, gitEnv, basessh)
}

// Return the executable name for ssh on this machine and the base args
// Base args includes port settings, user/host, everything pre the command to execute
func getExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata) (exe string, baseargs []string, needShell bool) {
var cmd string

isPlink := false
isTortoise := false

ssh, _ := osEnv.Get("GIT_SSH")
sshCmd, _ := osEnv.Get("GIT_SSH_COMMAND")
ssh, cmd, needShell = parseShellCommand(sshCmd, ssh)
Expand All @@ -69,25 +123,17 @@ func getExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SS
}

basessh := filepath.Base(ssh)

if basessh != defaultSSHCmd {
// Strip extension for easier comparison
if ext := filepath.Ext(basessh); len(ext) > 0 {
basessh = basessh[:len(basessh)-len(ext)]
}
isPlink = strings.EqualFold(basessh, "plink")
isTortoise = strings.EqualFold(basessh, "tortoiseplink")
}
variant := getVariant(osEnv, gitEnv, basessh)

args := make([]string, 0, 7)

if isTortoise {
if variant == variantTortoise {
// TortoisePlink requires the -batch argument to behave like ssh/plink
args = append(args, "-batch")
}

if len(meta.Port) > 0 {
if isPlink || isTortoise {
if variant == variantPutty || variant == variantTortoise {
args = append(args, "-P")
} else {
args = append(args, "-p")
Expand Down
98 changes: 98 additions & 0 deletions ssh/ssh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,44 @@ func TestSSHGetExeAndArgsPlinkCustomPort(t *testing.T) {
assert.Equal(t, []string{"-P", "8888", "[email protected]"}, args)
}

func TestSSHGetExeAndArgsPlinkCustomPortExplicitEnvironment(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "ssh")

cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
"GIT_SSH_VARIANT": "plink",
}, nil))
require.Nil(t, err)

meta := SSHMetadata{}
meta.UserAndHost = "[email protected]"
meta.Port = "8888"

exe, args := formatArgs(getExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"-P", "8888", "[email protected]"}, args)
}

func TestSSHGetExeAndArgsPlinkCustomPortExplicitEnvironmentPutty(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "ssh")

cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
"GIT_SSH_VARIANT": "putty",
}, nil))
require.Nil(t, err)

meta := SSHMetadata{}
meta.UserAndHost = "[email protected]"
meta.Port = "8888"

exe, args := formatArgs(getExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"-P", "8888", "[email protected]"}, args)
}

func TestSSHGetExeAndArgsTortoisePlink(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink.exe")

Expand Down Expand Up @@ -152,6 +190,66 @@ func TestSSHGetExeAndArgsTortoisePlinkCustomPort(t *testing.T) {
assert.Equal(t, []string{"-batch", "-P", "8888", "[email protected]"}, args)
}

func TestSSHGetExeAndArgsTortoisePlinkCustomPortExplicitEnvironment(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "ssh")

cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
"GIT_SSH_VARIANT": "tortoiseplink",
}, nil))
require.Nil(t, err)

meta := SSHMetadata{}
meta.UserAndHost = "[email protected]"
meta.Port = "8888"

exe, args := formatArgs(getExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"-batch", "-P", "8888", "[email protected]"}, args)
}

func TestSSHGetExeAndArgsTortoisePlinkCustomPortExplicitConfig(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "ssh")

cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
"GIT_SSH_VARIANT": "tortoiseplink",
}, map[string]string{
"ssh.variant": "tortoiseplink",
}))
require.Nil(t, err)

meta := SSHMetadata{}
meta.UserAndHost = "[email protected]"
meta.Port = "8888"

exe, args := formatArgs(getExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"-batch", "-P", "8888", "[email protected]"}, args)
}

func TestSSHGetExeAndArgsTortoisePlinkCustomPortExplicitConfigOverride(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "ssh")

cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
}, map[string]string{
"ssh.variant": "putty",
}))
require.Nil(t, err)

meta := SSHMetadata{}
meta.UserAndHost = "[email protected]"
meta.Port = "8888"

exe, args := formatArgs(getExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"-batch", "-P", "8888", "[email protected]"}, args)
}

func TestSSHGetExeAndArgsSshCommandPrecedence(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "sshcmd",
Expand Down

0 comments on commit 9114a57

Please sign in to comment.