Skip to content

Commit 9d69005

Browse files
authored
Merge pull request git-lfs#5905 from chrisd8088/ssh-batch-multi-fix
Fix crash during pure SSH object transfer with multiple objects
2 parents f979a7d + f4fdb5c commit 9d69005

File tree

8 files changed

+313
-96
lines changed

8 files changed

+313
-96
lines changed

docs/man/git-lfs-config.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ keepalive connections. Default: 30 minutes.
9191
+
9292
When using the pure SSH-based protocol, whether to multiplex requests
9393
over a single connection when possible. This option requires the use of
94-
OpenSSH or a compatible SSH client. Default: true.
94+
OpenSSH or a compatible SSH client. Default: false on Windows, otherwise
95+
true.
9596
* `lfs.ssh.retries`
9697
+
9798
Specifies the number of times Git LFS will attempt to obtain

lfsapi/lfsapi.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,14 @@ func (c *Client) SSHTransfer(operation, remote string) *ssh.SSHTransfer {
6262
}
6363
uc := config.NewURLConfig(c.context.GitEnv())
6464
if val, ok := uc.Get("lfs", endpoint.OriginalUrl, "sshtransfer"); ok && val != "negotiate" && val != "always" {
65-
tracerx.Printf("skipping pure SSH protocol connection by request")
65+
tracerx.Printf("skipping pure SSH protocol connection by request (%s, %s)", operation, remote)
6666
return nil
6767
}
6868
ctx := c.Context()
69-
tracerx.Printf("attempting pure SSH protocol connection")
69+
tracerx.Printf("attempting pure SSH protocol connection (%s, %s)", operation, remote)
7070
sshTransfer, err := ssh.NewSSHTransfer(ctx.OSEnv(), ctx.GitEnv(), &endpoint.SSHMetadata, operation)
7171
if err != nil {
72-
tracerx.Printf("pure SSH protocol connection failed: %s", err)
72+
tracerx.Printf("pure SSH protocol connection failed (%s, %s): %s", operation, remote, err)
7373
return nil
7474
}
7575
return sshTransfer

ssh/connection.go

Lines changed: 59 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func NewSSHTransfer(osEnv config.Environment, gitEnv config.Environment, meta *S
4242
}
4343

4444
func startConnection(id int, osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, operation string, multiplexControlPath string) (conn *PktlineConnection, multiplexing bool, controlPath string, err error) {
45-
tracerx.Printf("spawning pure SSH connection")
45+
tracerx.Printf("spawning pure SSH connection (#%d)", id)
4646
var errbuf bytes.Buffer
4747
exe, args, multiplexing, controlPath := GetLFSExeAndArgs(osEnv, gitEnv, meta, "git-lfs-transfer", operation, true, multiplexControlPath)
4848
cmd, err := subprocess.ExecCommand(exe, args...)
@@ -81,125 +81,128 @@ func startConnection(id int, osEnv config.Environment, gitEnv config.Environment
8181
w.Close()
8282
cmd.Wait()
8383
err = errors.Combine([]error{err, fmt.Errorf(tr.Tr.Get("Failed to connect to remote SSH server: %s", cmd.Stderr))})
84+
tracerx.Printf("pure SSH connection unsuccessful (#%d)", id)
85+
} else {
86+
tracerx.Printf("pure SSH connection successful (#%d)", id)
8487
}
85-
tracerx.Printf("pure SSH connection successful")
8688
return conn, multiplexing, controlPath, err
8789
}
8890

8991
// Connection returns the nth connection (starting from 0) in this transfer
9092
// instance or nil if there is no such item.
91-
func (tr *SSHTransfer) IsMultiplexingEnabled() bool {
92-
return tr.multiplexing
93+
func (st *SSHTransfer) IsMultiplexingEnabled() bool {
94+
return st.multiplexing
9395
}
9496

9597
// Connection returns the nth connection (starting from 0) in this transfer
9698
// instance if it is initialized and otherwise initializes a new connection and
97-
// saves it in the nth position. In all cases, nil is returned if n is greater
98-
// than the maximum number of connections.
99-
func (tr *SSHTransfer) Connection(n int) (*PktlineConnection, error) {
100-
tr.lock.RLock()
101-
if n >= len(tr.conn) {
102-
tr.lock.RUnlock()
103-
return nil, nil
104-
}
105-
if tr.conn[n] != nil {
106-
defer tr.lock.RUnlock()
107-
return tr.conn[n], nil
108-
}
109-
tr.lock.RUnlock()
99+
// saves it in the nth position. In all cases, nil is returned with an error
100+
// if n is greater than the maximum number of connections, including when
101+
// the connection array itself is nil.
102+
func (st *SSHTransfer) Connection(n int) (*PktlineConnection, error) {
103+
st.lock.RLock()
104+
if n >= len(st.conn) {
105+
st.lock.RUnlock()
106+
return nil, errors.New(tr.Tr.Get("pure SSH connection unavailable (#%d)", n))
107+
}
108+
if st.conn[n] != nil {
109+
defer st.lock.RUnlock()
110+
return st.conn[n], nil
111+
}
112+
st.lock.RUnlock()
110113

111-
tr.lock.Lock()
112-
defer tr.lock.Unlock()
113-
if tr.conn[n] != nil {
114-
return tr.conn[n], nil
114+
st.lock.Lock()
115+
defer st.lock.Unlock()
116+
if st.conn[n] != nil {
117+
return st.conn[n], nil
115118
}
116-
conn, _, err := tr.spawnConnection(n)
119+
conn, _, err := st.spawnConnection(n)
117120
if err != nil {
118121
return nil, err
119122
}
120-
tr.conn[n] = conn
123+
st.conn[n] = conn
121124
return conn, nil
122125
}
123126

124127
// ConnectionCount returns the number of connections this object has.
125-
func (tr *SSHTransfer) ConnectionCount() int {
126-
tr.lock.RLock()
127-
defer tr.lock.RUnlock()
128-
return len(tr.conn)
128+
func (st *SSHTransfer) ConnectionCount() int {
129+
st.lock.RLock()
130+
defer st.lock.RUnlock()
131+
return len(st.conn)
129132
}
130133

131134
// SetConnectionCount sets the number of connections to the specified number.
132-
func (tr *SSHTransfer) SetConnectionCount(n int) error {
133-
tr.lock.Lock()
134-
defer tr.lock.Unlock()
135-
return tr.setConnectionCount(n)
135+
func (st *SSHTransfer) SetConnectionCount(n int) error {
136+
st.lock.Lock()
137+
defer st.lock.Unlock()
138+
return st.setConnectionCount(n)
136139
}
137140

138141
// SetConnectionCountAtLeast sets the number of connections to be not less than
139142
// the specified number.
140-
func (tr *SSHTransfer) SetConnectionCountAtLeast(n int) error {
141-
tr.lock.Lock()
142-
defer tr.lock.Unlock()
143-
count := len(tr.conn)
143+
func (st *SSHTransfer) SetConnectionCountAtLeast(n int) error {
144+
st.lock.Lock()
145+
defer st.lock.Unlock()
146+
count := len(st.conn)
144147
if n <= count {
145148
return nil
146149
}
147-
return tr.setConnectionCount(n)
150+
return st.setConnectionCount(n)
148151
}
149152

150-
func (tr *SSHTransfer) spawnConnection(n int) (*PktlineConnection, string, error) {
151-
conn, _, controlPath, err := startConnection(n, tr.osEnv, tr.gitEnv, tr.meta, tr.operation, tr.controlPath)
153+
func (st *SSHTransfer) spawnConnection(n int) (*PktlineConnection, string, error) {
154+
conn, _, controlPath, err := startConnection(n, st.osEnv, st.gitEnv, st.meta, st.operation, st.controlPath)
152155
if err != nil {
153-
tracerx.Printf("failed to spawn pure SSH connection: %s", err)
156+
tracerx.Printf("failed to spawn pure SSH connection (#%d): %s", n, err)
154157
return nil, "", err
155158
}
156159
return conn, controlPath, err
157160
}
158161

159-
func (tr *SSHTransfer) setConnectionCount(n int) error {
160-
count := len(tr.conn)
162+
func (st *SSHTransfer) setConnectionCount(n int) error {
163+
count := len(st.conn)
161164
if n < count {
162165
tn := n
163166
if tn == 0 {
164167
tn = 1
165168
}
166-
for _, item := range tr.conn[tn:count] {
169+
for i, item := range st.conn[tn:count] {
167170
if item == nil {
168-
tracerx.Printf("skipping uninitialized lazy pure SSH connection (%d -> %d)", count, n)
171+
tracerx.Printf("skipping uninitialized lazy pure SSH connection (#%d) (resetting total from %d to %d)", i, count, n)
169172
continue
170173
}
171-
tracerx.Printf("terminating pure SSH connection (%d -> %d)", count, n)
174+
tracerx.Printf("terminating pure SSH connection (#%d) (resetting total from %d to %d)", tn+i, count, n)
172175
if err := item.End(); err != nil {
173176
return err
174177
}
175178
}
176-
tr.conn = tr.conn[0:tn]
179+
st.conn = st.conn[0:tn]
177180
} else if n > count {
178181
for i := count; i < n; i++ {
179182
if i == 0 {
180-
conn, controlPath, err := tr.spawnConnection(i)
183+
conn, controlPath, err := st.spawnConnection(i)
181184
if err != nil {
182185
return err
183186
}
184-
tr.conn = append(tr.conn, conn)
185-
tr.controlPath = controlPath
187+
st.conn = append(st.conn, conn)
188+
st.controlPath = controlPath
186189
} else {
187-
tr.conn = append(tr.conn, nil)
190+
st.conn = append(st.conn, nil)
188191
}
189192
}
190193
}
191194
if n == 0 && count > 0 {
192-
tracerx.Printf("terminating pure SSH connection (%d -> %d)", count, n)
193-
if err := tr.conn[0].End(); err != nil {
195+
tracerx.Printf("terminating pure SSH connection (#0) (resetting total from %d to %d)", count, n)
196+
if err := st.conn[0].End(); err != nil {
194197
return err
195198
}
196-
tr.conn = nil
197-
tr.controlPath = ""
199+
st.conn = nil
200+
st.controlPath = ""
198201
}
199202
return nil
200203
}
201204

202-
func (tr *SSHTransfer) Shutdown() error {
203-
tracerx.Printf("shutting down pure SSH connection")
204-
return tr.SetConnectionCount(0)
205+
func (st *SSHTransfer) Shutdown() error {
206+
tracerx.Printf("shutting down pure SSH connections")
207+
return st.SetConnectionCount(0)
205208
}

ssh/ssh.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type SSHMetadata struct {
3030
Path string
3131
}
3232

33-
func FormatArgs(cmd string, args []string, needShell bool, multiplex bool, controlPath string) (string, []string) {
33+
func FormatArgs(cmd string, args []string, needShell bool) (string, []string) {
3434
if !needShell {
3535
return cmd, args
3636
}
@@ -41,7 +41,7 @@ func FormatArgs(cmd string, args []string, needShell bool, multiplex bool, contr
4141
func GetLFSExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, command, operation string, multiplexDesired bool, multiplexControlPath string) (exe string, args []string, multiplexing bool, controlPath string) {
4242
exe, args, needShell, multiplexing, controlPath := GetExeAndArgs(osEnv, gitEnv, meta, multiplexDesired, multiplexControlPath)
4343
args = append(args, fmt.Sprintf("%s %s %s", command, meta.Path, operation))
44-
exe, args = FormatArgs(exe, args, needShell, multiplexing, controlPath)
44+
exe, args = FormatArgs(exe, args, needShell)
4545
tracerx.Printf("run_command: %s %s", exe, strings.Join(args, " "))
4646
return exe, args, multiplexing, controlPath
4747
}

0 commit comments

Comments
 (0)