Move much of SSH code into a separate package

In the future, we'll want to call into the SSH code from multiple
packages, so let's move it out of the lfshttp package into its own
package to avoid package import loops.  While we're at it, rename the
function names to remove the "ssh" prefix, since it's implied by the
fact that they're in a package called "ssh".

Move the tests to their own package to prevent an import loop and expose
the private functions so we can test them there.
This commit is contained in:
brian m. carlson 2021-02-03 22:27:01 +00:00
parent e9ffd5dc5c
commit 42e08e18b1
No known key found for this signature in database
GPG Key ID: 2D0C9BC12F82B3A1
7 changed files with 572 additions and 566 deletions

@ -126,6 +126,7 @@ PKGS += lfs
PKGS += lfsapi
PKGS += lfshttp
PKGS += locking
PKGS += ssh
PKGS += subprocess
PKGS += tasklog
PKGS += tools

@ -8,6 +8,7 @@ import (
"github.com/git-lfs/git-lfs/creds"
"github.com/git-lfs/git-lfs/lfshttp"
"github.com/git-lfs/git-lfs/ssh"
"github.com/stretchr/testify/assert"
)
@ -557,7 +558,7 @@ func TestEndpointParsing(t *testing.T) {
"git@github.com:git-lfs/git-lfs.git",
lfshttp.Endpoint{
Url: "https://github.com/git-lfs/git-lfs.git",
SSHMetadata: lfshttp.SSHMetadata{
SSHMetadata: ssh.SSHMetadata{
UserAndHost: "git@github.com",
Path: "git-lfs/git-lfs.git",
Port: "",
@ -569,7 +570,7 @@ func TestEndpointParsing(t *testing.T) {
"[git@lfshttp.github.com:443]:git-lfs/git-lfs.git",
lfshttp.Endpoint{
Url: "https://lfshttp.github.com/git-lfs/git-lfs.git",
SSHMetadata: lfshttp.SSHMetadata{
SSHMetadata: ssh.SSHMetadata{
UserAndHost: "git@lfshttp.github.com",
Path: "git-lfs/git-lfs.git",
Port: "443",
@ -581,7 +582,7 @@ func TestEndpointParsing(t *testing.T) {
"github.com:git-lfs/git-lfs.git",
lfshttp.Endpoint{
Url: "https://github.com/git-lfs/git-lfs.git",
SSHMetadata: lfshttp.SSHMetadata{
SSHMetadata: ssh.SSHMetadata{
UserAndHost: "github.com",
Path: "git-lfs/git-lfs.git",
Port: "",
@ -593,7 +594,7 @@ func TestEndpointParsing(t *testing.T) {
"github:git-lfs/git-lfs.git",
lfshttp.Endpoint{
Url: "https://github/git-lfs/git-lfs.git",
SSHMetadata: lfshttp.SSHMetadata{
SSHMetadata: ssh.SSHMetadata{
UserAndHost: "github",
Path: "git-lfs/git-lfs.git",
Port: "",
@ -605,7 +606,7 @@ func TestEndpointParsing(t *testing.T) {
"gh:git-lfs/git-lfs.git",
lfshttp.Endpoint{
Url: "https://github.com/git-lfs/git-lfs.git",
SSHMetadata: lfshttp.SSHMetadata{
SSHMetadata: ssh.SSHMetadata{
UserAndHost: "",
Path: "",
Port: "",
@ -617,7 +618,7 @@ func TestEndpointParsing(t *testing.T) {
"remote::git-lfs/git-lfs.git",
lfshttp.Endpoint{
Url: "remote::git-lfs/git-lfs.git",
SSHMetadata: lfshttp.SSHMetadata{
SSHMetadata: ssh.SSHMetadata{
UserAndHost: "",
Path: "",
Port: "",
@ -656,7 +657,7 @@ func TestInsteadOf(t *testing.T) {
"download",
lfshttp.Endpoint{
Url: "https://example.com/git-lfs/git-lfs.git/info/lfs",
SSHMetadata: lfshttp.SSHMetadata{
SSHMetadata: ssh.SSHMetadata{
UserAndHost: "",
Path: "",
Port: "",
@ -669,7 +670,7 @@ func TestInsteadOf(t *testing.T) {
"upload",
lfshttp.Endpoint{
Url: "https://example.com/git-lfs/git-lfs.git/info/lfs",
SSHMetadata: lfshttp.SSHMetadata{
SSHMetadata: ssh.SSHMetadata{
UserAndHost: "example.com",
Path: "git-lfs/git-lfs.git",
Port: "",
@ -682,7 +683,7 @@ func TestInsteadOf(t *testing.T) {
"download",
lfshttp.Endpoint{
Url: "https://example.com/git-lfs/git-lfs.git/info/lfs",
SSHMetadata: lfshttp.SSHMetadata{
SSHMetadata: ssh.SSHMetadata{
UserAndHost: "example.com",
Path: "git-lfs/git-lfs.git",
Port: "",
@ -695,7 +696,7 @@ func TestInsteadOf(t *testing.T) {
"upload",
lfshttp.Endpoint{
Url: "https://example.com/git-lfs/git-lfs.git/info/lfs",
SSHMetadata: lfshttp.SSHMetadata{
SSHMetadata: ssh.SSHMetadata{
UserAndHost: "example.com",
Path: "git-lfs/git-lfs.git",
Port: "",

@ -7,6 +7,7 @@ import (
"strings"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/ssh"
)
const UrlUnknown = "<unknown>"
@ -14,7 +15,7 @@ const UrlUnknown = "<unknown>"
// An Endpoint describes how to access a Git LFS server.
type Endpoint struct {
Url string
SSHMetadata SSHMetadata
SSHMetadata ssh.SSHMetadata
Operation string
}

@ -3,13 +3,11 @@ package lfshttp
import (
"bytes"
"encoding/json"
"fmt"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/ssh"
"github.com/git-lfs/git-lfs/subprocess"
"github.com/git-lfs/git-lfs/tools"
"github.com/rubyist/tracerx"
@ -81,7 +79,7 @@ func (c *sshAuthClient) Resolve(e Endpoint, method string) (sshAuthResponse, err
return res, nil
}
exe, args := sshGetLFSExeAndArgs(c.os, c.git, &e.SSHMetadata, endpointOperation(e, method), method)
exe, args := ssh.GetLFSExeAndArgs(c.os, c.git, &e.SSHMetadata, endpointOperation(e, method), method)
cmd := subprocess.ExecCommand(exe, args...)
// Save stdout and stderr in separate buffers
@ -114,113 +112,3 @@ func (c *sshAuthClient) Resolve(e Endpoint, method string) (sshAuthResponse, err
return res, err
}
type SSHMetadata struct {
UserAndHost string
Port string
Path string
}
func sshFormatArgs(cmd string, args []string, needShell bool) (string, []string) {
if !needShell {
return cmd, args
}
return subprocess.FormatForShellQuotedArgs(cmd, args)
}
func sshGetLFSExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, operation, method string) (string, []string) {
exe, args, needShell := sshGetExeAndArgs(osEnv, gitEnv, meta)
args = append(args, fmt.Sprintf("git-lfs-authenticate %s %s", meta.Path, operation))
exe, args = sshFormatArgs(exe, args, needShell)
tracerx.Printf("run_command: %s %s", exe, strings.Join(args, " "))
return exe, args
}
// Parse command, and if it looks like a valid command, return the ssh binary
// name, the command to run, and whether we need a shell. If not, return
// existing as the ssh binary name.
func sshParseShellCommand(command string, existing string) (ssh string, cmd string, needShell bool) {
ssh = existing
if cmdArgs := tools.QuotedFields(command); len(cmdArgs) > 0 {
needShell = true
ssh = cmdArgs[0]
cmd = command
}
return
}
// 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 sshGetExeAndArgs(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 = sshParseShellCommand(sshCmd, ssh)
if ssh == "" {
sshCmd, _ := gitEnv.Get("core.sshcommand")
ssh, cmd, needShell = sshParseShellCommand(sshCmd, defaultSSHCmd)
}
if cmd == "" {
cmd = ssh
}
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")
}
args := make([]string, 0, 7)
if isTortoise {
// TortoisePlink requires the -batch argument to behave like ssh/plink
args = append(args, "-batch")
}
if len(meta.Port) > 0 {
if isPlink || isTortoise {
args = append(args, "-P")
} else {
args = append(args, "-p")
}
args = append(args, meta.Port)
}
if sep, ok := sshSeparators[basessh]; ok {
// inserts a separator between cli -options and host/cmd commands
// example: $ ssh -p 12345 -- user@host.com git-lfs-authenticate ...
args = append(args, sep, meta.UserAndHost)
} else {
// no prefix supported, strip leading - off host to prevent cmd like:
// $ git config lfs.url ssh://-proxycmd=whatever
// $ plink -P 12345 -proxycmd=foo git-lfs-authenticate ...
//
// Instead, it'll attempt this, and eventually return an error
// $ plink -P 12345 proxycmd=foo git-lfs-authenticate ...
args = append(args, sshOptPrefixRE.ReplaceAllString(meta.UserAndHost, ""))
}
return cmd, args, needShell
}
const defaultSSHCmd = "ssh"
var (
sshOptPrefixRE = regexp.MustCompile(`\A\-+`)
sshSeparators = map[string]string{
"ssh": "--",
"lfs-ssh-echo": "--", // used in lfs integration tests only
}
)

@ -1,14 +1,12 @@
package lfshttp
import (
"net/url"
"path/filepath"
"testing"
"time"
"github.com/git-lfs/git-lfs/errors"
sshp "github.com/git-lfs/git-lfs/ssh"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSSHCacheResolveFromCache(t *testing.T) {
@ -21,7 +19,7 @@ func TestSSHCacheResolveFromCache(t *testing.T) {
ssh.responses["userandhost"] = sshAuthResponse{Href: "real"}
e := Endpoint{
SSHMetadata: SSHMetadata{
SSHMetadata: sshp.SSHMetadata{
UserAndHost: "userandhost",
Port: "1",
Path: "path",
@ -44,7 +42,7 @@ func TestSSHCacheResolveFromCacheWithFutureExpiresAt(t *testing.T) {
ssh.responses["userandhost"] = sshAuthResponse{Href: "real"}
e := Endpoint{
SSHMetadata: SSHMetadata{
SSHMetadata: sshp.SSHMetadata{
UserAndHost: "userandhost",
Port: "1",
Path: "path",
@ -67,7 +65,7 @@ func TestSSHCacheResolveFromCacheWithFutureExpiresIn(t *testing.T) {
ssh.responses["userandhost"] = sshAuthResponse{Href: "real"}
e := Endpoint{
SSHMetadata: SSHMetadata{
SSHMetadata: sshp.SSHMetadata{
UserAndHost: "userandhost",
Port: "1",
Path: "path",
@ -90,7 +88,7 @@ func TestSSHCacheResolveFromCacheWithPastExpiresAt(t *testing.T) {
ssh.responses["userandhost"] = sshAuthResponse{Href: "real"}
e := Endpoint{
SSHMetadata: SSHMetadata{
SSHMetadata: sshp.SSHMetadata{
UserAndHost: "userandhost",
Port: "1",
Path: "path",
@ -113,7 +111,7 @@ func TestSSHCacheResolveFromCacheWithPastExpiresIn(t *testing.T) {
ssh.responses["userandhost"] = sshAuthResponse{Href: "real"}
e := Endpoint{
SSHMetadata: SSHMetadata{
SSHMetadata: sshp.SSHMetadata{
UserAndHost: "userandhost",
Port: "1",
Path: "path",
@ -137,7 +135,7 @@ func TestSSHCacheResolveFromCacheWithAmbiguousExpirationInfo(t *testing.T) {
ssh.responses["userandhost"] = sshAuthResponse{Href: "real"}
e := Endpoint{
SSHMetadata: SSHMetadata{
SSHMetadata: sshp.SSHMetadata{
UserAndHost: "userandhost",
Port: "1",
Path: "path",
@ -158,7 +156,7 @@ func TestSSHCacheResolveWithoutError(t *testing.T) {
ssh.responses["userandhost"] = sshAuthResponse{Href: "real"}
e := Endpoint{
SSHMetadata: SSHMetadata{
SSHMetadata: sshp.SSHMetadata{
UserAndHost: "userandhost",
Port: "1",
Path: "path",
@ -190,7 +188,7 @@ func TestSSHCacheResolveWithError(t *testing.T) {
ssh.responses["userandhost"] = sshAuthResponse{Message: "resolve error", Href: "real"}
e := Endpoint{
SSHMetadata: SSHMetadata{
SSHMetadata: sshp.SSHMetadata{
UserAndHost: "userandhost",
Port: "1",
Path: "path",
@ -227,433 +225,3 @@ func (r *fakeResolver) Resolve(e Endpoint, method string) (sshAuthResponse, erro
return res, err
}
func TestSSHGetLFSExeAndArgs(t *testing.T) {
cli, err := NewClient(nil)
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
endpoint.SSHMetadata.Path = "user/repo"
exe, args := sshGetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata, endpoint.Operation, "GET")
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{
"--",
"user@foo.com",
"git-lfs-authenticate user/repo download",
}, args)
exe, args = sshGetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata, endpoint.Operation, "HEAD")
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{
"--",
"user@foo.com",
"git-lfs-authenticate user/repo download",
}, args)
// this is going by endpoint.Operation, implicitly set by Endpoint() on L15.
exe, args = sshGetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata, endpoint.Operation, "POST")
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{
"--",
"user@foo.com",
"git-lfs-authenticate user/repo download",
}, args)
endpoint.Operation = "upload"
exe, args = sshGetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata, endpoint.Operation, "POST")
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{
"--",
"user@foo.com",
"git-lfs-authenticate user/repo upload",
}, args)
}
func TestSSHGetExeAndArgsSsh(t *testing.T) {
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": "",
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{"--", "user@foo.com"}, args)
}
func TestSSHGetExeAndArgsSshCustomPort(t *testing.T) {
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": "",
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
endpoint.SSHMetadata.Port = "8888"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{"-p", "8888", "--", "user@foo.com"}, args)
}
func TestSSHGetExeAndArgsPlink(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "plink.exe")
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"user@foo.com"}, args)
}
func TestSSHGetExeAndArgsPlinkCustomPort(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "plink")
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
endpoint.SSHMetadata.Port = "8888"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"-P", "8888", "user@foo.com"}, args)
}
func TestSSHGetExeAndArgsTortoisePlink(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink.exe")
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"-batch", "user@foo.com"}, args)
}
func TestSSHGetExeAndArgsTortoisePlinkCustomPort(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink")
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
endpoint.SSHMetadata.Port = "8888"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"-batch", "-P", "8888", "user@foo.com"}, args)
}
func TestSSHGetExeAndArgsSshCommandPrecedence(t *testing.T) {
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "sshcmd",
"GIT_SSH": "bad",
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd user@foo.com"}, args)
}
func TestSSHGetExeAndArgsSshCommandArgs(t *testing.T) {
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "sshcmd --args 1",
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd --args 1 user@foo.com"}, args)
}
func TestSSHGetExeAndArgsSshCommandArgsWithMixedQuotes(t *testing.T) {
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "sshcmd foo 'bar \"baz\"'",
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd foo 'bar \"baz\"' user@foo.com"}, args)
}
func TestSSHGetExeAndArgsSshCommandCustomPort(t *testing.T) {
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "sshcmd",
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
endpoint.SSHMetadata.Port = "8888"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd -p 8888 user@foo.com"}, args)
}
func TestSSHGetExeAndArgsCoreSshCommand(t *testing.T) {
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "sshcmd --args 2",
}, map[string]string{
"core.sshcommand": "sshcmd --args 1",
}))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd --args 2 user@foo.com"}, args)
}
func TestSSHGetExeAndArgsCoreSshCommandArgsWithMixedQuotes(t *testing.T) {
cli, err := NewClient(NewContext(nil, nil, map[string]string{
"core.sshcommand": "sshcmd foo 'bar \"baz\"'",
}))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd foo 'bar \"baz\"' user@foo.com"}, args)
}
func TestSSHGetExeAndArgsConfigVersusEnv(t *testing.T) {
cli, err := NewClient(NewContext(nil, nil, map[string]string{
"core.sshcommand": "sshcmd --args 1",
}))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd --args 1 user@foo.com"}, args)
}
func TestSSHGetLFSExeAndArgsWithCustomSSH(t *testing.T) {
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH": "not-ssh",
}, nil))
require.Nil(t, err)
u, err := url.Parse("ssh://git@host.com:12345/repo")
require.Nil(t, err)
e := EndpointFromSshUrl(u)
t.Logf("ENDPOINT: %+v", e)
assert.Equal(t, "12345", e.SSHMetadata.Port)
assert.Equal(t, "git@host.com", e.SSHMetadata.UserAndHost)
assert.Equal(t, "repo", e.SSHMetadata.Path)
exe, args := sshGetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "download", "GET")
assert.Equal(t, "not-ssh", exe)
assert.Equal(t, []string{"-p", "12345", "git@host.com", "git-lfs-authenticate repo download"}, args)
}
func TestSSHGetLFSExeAndArgsInvalidOptionsAsHost(t *testing.T) {
cli, err := NewClient(nil)
require.Nil(t, err)
u, err := url.Parse("ssh://-oProxyCommand=gnome-calculator/repo")
require.Nil(t, err)
assert.Equal(t, "-oProxyCommand=gnome-calculator", u.Host)
e := EndpointFromSshUrl(u)
t.Logf("ENDPOINT: %+v", e)
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SSHMetadata.UserAndHost)
assert.Equal(t, "repo", e.SSHMetadata.Path)
exe, args := sshGetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "download", "GET")
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{"--", "-oProxyCommand=gnome-calculator", "git-lfs-authenticate repo download"}, args)
}
func TestSSHGetLFSExeAndArgsInvalidOptionsAsHostWithCustomSSH(t *testing.T) {
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH": "not-ssh",
}, nil))
require.Nil(t, err)
u, err := url.Parse("ssh://--oProxyCommand=gnome-calculator/repo")
require.Nil(t, err)
assert.Equal(t, "--oProxyCommand=gnome-calculator", u.Host)
e := EndpointFromSshUrl(u)
t.Logf("ENDPOINT: %+v", e)
assert.Equal(t, "--oProxyCommand=gnome-calculator", e.SSHMetadata.UserAndHost)
assert.Equal(t, "repo", e.SSHMetadata.Path)
exe, args := sshGetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "download", "GET")
assert.Equal(t, "not-ssh", exe)
assert.Equal(t, []string{"oProxyCommand=gnome-calculator", "git-lfs-authenticate repo download"}, args)
}
func TestSSHGetExeAndArgsInvalidOptionsAsHost(t *testing.T) {
cli, err := NewClient(nil)
require.Nil(t, err)
u, err := url.Parse("ssh://-oProxyCommand=gnome-calculator")
require.Nil(t, err)
assert.Equal(t, "-oProxyCommand=gnome-calculator", u.Host)
e := EndpointFromSshUrl(u)
t.Logf("ENDPOINT: %+v", e)
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SSHMetadata.UserAndHost)
assert.Equal(t, "", e.SSHMetadata.Path)
exe, args, needShell := sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata)
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{"--", "-oProxyCommand=gnome-calculator"}, args)
assert.Equal(t, false, needShell)
}
func TestSSHGetExeAndArgsInvalidOptionsAsPath(t *testing.T) {
cli, err := NewClient(nil)
require.Nil(t, err)
u, err := url.Parse("ssh://git@git-host.com/-oProxyCommand=gnome-calculator")
require.Nil(t, err)
assert.Equal(t, "git-host.com", u.Host)
e := EndpointFromSshUrl(u)
t.Logf("ENDPOINT: %+v", e)
assert.Equal(t, "git@git-host.com", e.SSHMetadata.UserAndHost)
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SSHMetadata.Path)
exe, args, needShell := sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata)
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{"--", "git@git-host.com"}, args)
assert.Equal(t, false, needShell)
}
func TestParseBareSSHUrl(t *testing.T) {
e := EndpointFromBareSshUrl("git@git-host.com:repo.git")
t.Logf("endpoint: %+v", e)
assert.Equal(t, "git@git-host.com", e.SSHMetadata.UserAndHost)
assert.Equal(t, "repo.git", e.SSHMetadata.Path)
e = EndpointFromBareSshUrl("git@git-host.com/should-be-a-colon.git")
t.Logf("endpoint: %+v", e)
assert.Equal(t, "", e.SSHMetadata.UserAndHost)
assert.Equal(t, "", e.SSHMetadata.Path)
e = EndpointFromBareSshUrl("-oProxyCommand=gnome-calculator")
t.Logf("endpoint: %+v", e)
assert.Equal(t, "", e.SSHMetadata.UserAndHost)
assert.Equal(t, "", e.SSHMetadata.Path)
e = EndpointFromBareSshUrl("git@git-host.com:-oProxyCommand=gnome-calculator")
t.Logf("endpoint: %+v", e)
assert.Equal(t, "git@git-host.com", e.SSHMetadata.UserAndHost)
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SSHMetadata.Path)
}
func TestSSHGetExeAndArgsPlinkCommand(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "plink.exe")
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": plink,
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", plink + " user@foo.com"}, args)
}
func TestSSHGetExeAndArgsPlinkCommandCustomPort(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "plink")
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": plink,
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
endpoint.SSHMetadata.Port = "8888"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", plink + " -P 8888 user@foo.com"}, args)
}
func TestSSHGetExeAndArgsTortoisePlinkCommand(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink.exe")
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": plink,
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", plink + " -batch user@foo.com"}, args)
}
func TestSSHGetExeAndArgsTortoisePlinkCommandCustomPort(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink")
cli, err := NewClient(NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": plink,
}, nil))
require.Nil(t, err)
endpoint := Endpoint{Operation: "download"}
endpoint.SSHMetadata.UserAndHost = "user@foo.com"
endpoint.SSHMetadata.Port = "8888"
exe, args := sshFormatArgs(sshGetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &endpoint.SSHMetadata))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", plink + " -batch -P 8888 user@foo.com"}, args)
}

123
ssh/ssh.go Normal file

@ -0,0 +1,123 @@
package ssh
import (
"fmt"
"path/filepath"
"regexp"
"strings"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/subprocess"
"github.com/git-lfs/git-lfs/tools"
"github.com/rubyist/tracerx"
)
type SSHMetadata struct {
UserAndHost string
Port string
Path string
}
func FormatArgs(cmd string, args []string, needShell bool) (string, []string) {
if !needShell {
return cmd, args
}
return subprocess.FormatForShellQuotedArgs(cmd, args)
}
func GetLFSExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, operation, method string) (string, []string) {
exe, args, needShell := GetExeAndArgs(osEnv, gitEnv, meta)
args = append(args, fmt.Sprintf("git-lfs-authenticate %s %s", meta.Path, operation))
exe, args = FormatArgs(exe, args, needShell)
tracerx.Printf("run_command: %s %s", exe, strings.Join(args, " "))
return exe, args
}
// Parse command, and if it looks like a valid command, return the ssh binary
// name, the command to run, and whether we need a shell. If not, return
// existing as the ssh binary name.
func parseShellCommand(command string, existing string) (ssh string, cmd string, needShell bool) {
ssh = existing
if cmdArgs := tools.QuotedFields(command); len(cmdArgs) > 0 {
needShell = true
ssh = cmdArgs[0]
cmd = command
}
return
}
// 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)
if ssh == "" {
sshCmd, _ := gitEnv.Get("core.sshcommand")
ssh, cmd, needShell = parseShellCommand(sshCmd, defaultSSHCmd)
}
if cmd == "" {
cmd = ssh
}
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")
}
args := make([]string, 0, 7)
if isTortoise {
// TortoisePlink requires the -batch argument to behave like ssh/plink
args = append(args, "-batch")
}
if len(meta.Port) > 0 {
if isPlink || isTortoise {
args = append(args, "-P")
} else {
args = append(args, "-p")
}
args = append(args, meta.Port)
}
if sep, ok := sshSeparators[basessh]; ok {
// inserts a separator between cli -options and host/cmd commands
// example: $ ssh -p 12345 -- user@host.com git-lfs-authenticate ...
args = append(args, sep, meta.UserAndHost)
} else {
// no prefix supported, strip leading - off host to prevent cmd like:
// $ git config lfs.url ssh://-proxycmd=whatever
// $ plink -P 12345 -proxycmd=foo git-lfs-authenticate ...
//
// Instead, it'll attempt this, and eventually return an error
// $ plink -P 12345 proxycmd=foo git-lfs-authenticate ...
args = append(args, sshOptPrefixRE.ReplaceAllString(meta.UserAndHost, ""))
}
return cmd, args, needShell
}
const defaultSSHCmd = "ssh"
var (
sshOptPrefixRE = regexp.MustCompile(`\A\-+`)
sshSeparators = map[string]string{
"ssh": "--",
"lfs-ssh-echo": "--", // used in lfs integration tests only
}
)

424
ssh/ssh_test.go Normal file

@ -0,0 +1,424 @@
package ssh_test
import (
"net/url"
"path/filepath"
"testing"
"github.com/git-lfs/git-lfs/lfshttp"
"github.com/git-lfs/git-lfs/ssh"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSSHGetLFSExeAndArgs(t *testing.T) {
cli, err := lfshttp.NewClient(nil)
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
meta.Path = "user/repo"
exe, args := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, "download", "GET")
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{
"--",
"user@foo.com",
"git-lfs-authenticate user/repo download",
}, args)
exe, args = ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, "upload", "GET")
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{
"--",
"user@foo.com",
"git-lfs-authenticate user/repo upload",
}, args)
}
func TestSSHGetExeAndArgsSsh(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": "",
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{"--", "user@foo.com"}, args)
}
func TestSSHGetExeAndArgsSshCustomPort(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": "",
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
meta.Port = "8888"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{"-p", "8888", "--", "user@foo.com"}, args)
}
func TestSSHGetExeAndArgsPlink(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "plink.exe")
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"user@foo.com"}, args)
}
func TestSSHGetExeAndArgsPlinkCustomPort(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "plink")
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
meta.Port = "8888"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"-P", "8888", "user@foo.com"}, args)
}
func TestSSHGetExeAndArgsTortoisePlink(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink.exe")
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"-batch", "user@foo.com"}, args)
}
func TestSSHGetExeAndArgsTortoisePlinkCustomPort(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink")
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "",
"GIT_SSH": plink,
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
meta.Port = "8888"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, plink, exe)
assert.Equal(t, []string{"-batch", "-P", "8888", "user@foo.com"}, args)
}
func TestSSHGetExeAndArgsSshCommandPrecedence(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "sshcmd",
"GIT_SSH": "bad",
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd user@foo.com"}, args)
}
func TestSSHGetExeAndArgsSshCommandArgs(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "sshcmd --args 1",
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd --args 1 user@foo.com"}, args)
}
func TestSSHGetExeAndArgsSshCommandArgsWithMixedQuotes(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "sshcmd foo 'bar \"baz\"'",
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd foo 'bar \"baz\"' user@foo.com"}, args)
}
func TestSSHGetExeAndArgsSshCommandCustomPort(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "sshcmd",
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
meta.Port = "8888"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd -p 8888 user@foo.com"}, args)
}
func TestSSHGetExeAndArgsCoreSshCommand(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": "sshcmd --args 2",
}, map[string]string{
"core.sshcommand": "sshcmd --args 1",
}))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd --args 2 user@foo.com"}, args)
}
func TestSSHGetExeAndArgsCoreSshCommandArgsWithMixedQuotes(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
"core.sshcommand": "sshcmd foo 'bar \"baz\"'",
}))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd foo 'bar \"baz\"' user@foo.com"}, args)
}
func TestSSHGetExeAndArgsConfigVersusEnv(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
"core.sshcommand": "sshcmd --args 1",
}))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", "sshcmd --args 1 user@foo.com"}, args)
}
func TestSSHGetExeAndArgsPlinkCommand(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "plink.exe")
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": plink,
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", plink + " user@foo.com"}, args)
}
func TestSSHGetExeAndArgsPlinkCommandCustomPort(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "plink")
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": plink,
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
meta.Port = "8888"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", plink + " -P 8888 user@foo.com"}, args)
}
func TestSSHGetExeAndArgsTortoisePlinkCommand(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink.exe")
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": plink,
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", plink + " -batch user@foo.com"}, args)
}
func TestSSHGetExeAndArgsTortoisePlinkCommandCustomPort(t *testing.T) {
plink := filepath.Join("Users", "joebloggs", "bin", "tortoiseplink")
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH_COMMAND": plink,
}, nil))
require.Nil(t, err)
meta := ssh.SSHMetadata{}
meta.UserAndHost = "user@foo.com"
meta.Port = "8888"
exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta))
assert.Equal(t, "sh", exe)
assert.Equal(t, []string{"-c", plink + " -batch -P 8888 user@foo.com"}, args)
}
func TestSSHGetLFSExeAndArgsWithCustomSSH(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH": "not-ssh",
}, nil))
require.Nil(t, err)
u, err := url.Parse("ssh://git@host.com:12345/repo")
require.Nil(t, err)
e := lfshttp.EndpointFromSshUrl(u)
t.Logf("ENDPOINT: %+v", e)
assert.Equal(t, "12345", e.SSHMetadata.Port)
assert.Equal(t, "git@host.com", e.SSHMetadata.UserAndHost)
assert.Equal(t, "repo", e.SSHMetadata.Path)
exe, args := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "download", "GET")
assert.Equal(t, "not-ssh", exe)
assert.Equal(t, []string{"-p", "12345", "git@host.com", "git-lfs-authenticate repo download"}, args)
}
func TestSSHGetLFSExeAndArgsInvalidOptionsAsHost(t *testing.T) {
cli, err := lfshttp.NewClient(nil)
require.Nil(t, err)
u, err := url.Parse("ssh://-oProxyCommand=gnome-calculator/repo")
require.Nil(t, err)
assert.Equal(t, "-oProxyCommand=gnome-calculator", u.Host)
e := lfshttp.EndpointFromSshUrl(u)
t.Logf("ENDPOINT: %+v", e)
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SSHMetadata.UserAndHost)
assert.Equal(t, "repo", e.SSHMetadata.Path)
exe, args := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "download", "GET")
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{"--", "-oProxyCommand=gnome-calculator", "git-lfs-authenticate repo download"}, args)
}
func TestSSHGetLFSExeAndArgsInvalidOptionsAsHostWithCustomSSH(t *testing.T) {
cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
"GIT_SSH": "not-ssh",
}, nil))
require.Nil(t, err)
u, err := url.Parse("ssh://--oProxyCommand=gnome-calculator/repo")
require.Nil(t, err)
assert.Equal(t, "--oProxyCommand=gnome-calculator", u.Host)
e := lfshttp.EndpointFromSshUrl(u)
t.Logf("ENDPOINT: %+v", e)
assert.Equal(t, "--oProxyCommand=gnome-calculator", e.SSHMetadata.UserAndHost)
assert.Equal(t, "repo", e.SSHMetadata.Path)
exe, args := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "download", "GET")
assert.Equal(t, "not-ssh", exe)
assert.Equal(t, []string{"oProxyCommand=gnome-calculator", "git-lfs-authenticate repo download"}, args)
}
func TestSSHGetExeAndArgsInvalidOptionsAsHost(t *testing.T) {
cli, err := lfshttp.NewClient(nil)
require.Nil(t, err)
u, err := url.Parse("ssh://-oProxyCommand=gnome-calculator")
require.Nil(t, err)
assert.Equal(t, "-oProxyCommand=gnome-calculator", u.Host)
e := lfshttp.EndpointFromSshUrl(u)
t.Logf("ENDPOINT: %+v", e)
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SSHMetadata.UserAndHost)
assert.Equal(t, "", e.SSHMetadata.Path)
exe, args, needShell := ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata)
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{"--", "-oProxyCommand=gnome-calculator"}, args)
assert.Equal(t, false, needShell)
}
func TestSSHGetExeAndArgsInvalidOptionsAsPath(t *testing.T) {
cli, err := lfshttp.NewClient(nil)
require.Nil(t, err)
u, err := url.Parse("ssh://git@git-host.com/-oProxyCommand=gnome-calculator")
require.Nil(t, err)
assert.Equal(t, "git-host.com", u.Host)
e := lfshttp.EndpointFromSshUrl(u)
t.Logf("ENDPOINT: %+v", e)
assert.Equal(t, "git@git-host.com", e.SSHMetadata.UserAndHost)
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SSHMetadata.Path)
exe, args, needShell := ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata)
assert.Equal(t, "ssh", exe)
assert.Equal(t, []string{"--", "git@git-host.com"}, args)
assert.Equal(t, false, needShell)
}
func TestParseBareSSHUrl(t *testing.T) {
e := lfshttp.EndpointFromBareSshUrl("git@git-host.com:repo.git")
t.Logf("endpoint: %+v", e)
assert.Equal(t, "git@git-host.com", e.SSHMetadata.UserAndHost)
assert.Equal(t, "repo.git", e.SSHMetadata.Path)
e = lfshttp.EndpointFromBareSshUrl("git@git-host.com/should-be-a-colon.git")
t.Logf("endpoint: %+v", e)
assert.Equal(t, "", e.SSHMetadata.UserAndHost)
assert.Equal(t, "", e.SSHMetadata.Path)
e = lfshttp.EndpointFromBareSshUrl("-oProxyCommand=gnome-calculator")
t.Logf("endpoint: %+v", e)
assert.Equal(t, "", e.SSHMetadata.UserAndHost)
assert.Equal(t, "", e.SSHMetadata.Path)
e = lfshttp.EndpointFromBareSshUrl("git@git-host.com:-oProxyCommand=gnome-calculator")
t.Logf("endpoint: %+v", e)
assert.Equal(t, "git@git-host.com", e.SSHMetadata.UserAndHost)
assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SSHMetadata.Path)
}