Merge ~bartvdbraak/ubuntu/+source/git-lfs:ubuntu/noble-updates into ubuntu/+source/git-lfs:ubuntu/noble-updates

Proposed by Bart
Status: Needs review
Proposed branch: ~bartvdbraak/ubuntu/+source/git-lfs:ubuntu/noble-updates
Merge into: ubuntu/+source/git-lfs:ubuntu/noble-updates
Diff against target: 908 lines (+876/-0)
5 files modified
debian/changelog (+14/-0)
debian/patches/0001-ssh-Specifically-designate-a-master-multiplex-connec.patch (+641/-0)
debian/patches/0002-tq-transfer_test.go-enable-and-fix-all-tests.patch (+181/-0)
debian/patches/0003-tq-transfer-copy-Id-and-Token.patch (+37/-0)
debian/patches/series (+3/-0)
Reviewer Review Type Date Requested Status
Ubuntu Sponsors Pending
Review via email: mp+499577@code.launchpad.net

Commit message

Backport fixes from upstream that resolve Git LFS over SSH hanging issues (Closes: #2131243)

Description of the change

Fixes https://bugs.launchpad.net/ubuntu/+source/git-lfs/+bug/2131243
Upstream report https://github.com/git-lfs/git-lfs/issues/6017

Includes 3 separate commits from the upstream 3.5.0 release:

- ssh: Specifically designate a master multiplex connection
- tq/transfer_test.go: enable and fix all tests
- tq/transfer: copy Id and Token

To post a comment you must log in.

Unmerged commits

19a9bd2... by Bart van der Braak <email address hidden>

3.4.1-1ubuntu0.4 (patches unapplied)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index 0841cfd..bc5669c 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,3 +1,17 @@
6+git-lfs (3.4.1-1ubuntu0.4) noble; urgency=medium
7+
8+ * d/patches: Fix SSH push failures over multiplexed connections
9+ (LP: #2131243)
10+ - 0001-ssh-Specifically-designate-a-master-multiplex-connec.patch:
11+ Properly designate master SSH multiplex connection and shutdown
12+ sequence to prevent deadlock
13+ - 0002-tq-transfer_test.go-enable-and-fix-all-tests.patch:
14+ Enable and fix transfer adapter tests
15+ - 0003-tq-transfer-copy-Id-and-Token.patch:
16+ Copy Id and Token fields for SSH batch operations
17+
18+ -- Bart van der Braak <bart@blender.org> Thu, 30 Jan 2026 14:30:00 +0100
19+
20 git-lfs (3.4.1-1ubuntu0.3) noble-security; urgency=medium
21
22 * No change rebuild due to golang-1.22 update
23diff --git a/debian/patches/0001-ssh-Specifically-designate-a-master-multiplex-connec.patch b/debian/patches/0001-ssh-Specifically-designate-a-master-multiplex-connec.patch
24new file mode 100644
25index 0000000..6b385f8
26--- /dev/null
27+++ b/debian/patches/0001-ssh-Specifically-designate-a-master-multiplex-connec.patch
28@@ -0,0 +1,641 @@
29+From 87782e256a955d1e23f056d07be977a5f61fa203 Mon Sep 17 00:00:00 2001
30+From: Kyle Edwards <kyle.edwards@kitware.com>
31+Date: Wed, 4 Oct 2023 10:55:15 -0400
32+Subject: [PATCH 1/3] ssh: Specifically designate a master multiplex connection
33+
34+SSHTransfer.Shutdown() was attempting to first shut down the first
35+connection it created (which happened to be the master connection),
36+but this deadlocked because the master connection was waiting for
37+the extra connections to shut down. Designate the first connection
38+as the master connection, make sure that it truly is the master
39+connection, and shut it down after shutting down all extra
40+connections.
41+
42+Issue: #5535
43+---
44+ lfshttp/ssh.go | 2 +-
45+ ssh/connection.go | 41 ++++++++++-----
46+ ssh/ssh.go | 45 ++++++++---------
47+ ssh/ssh_test.go | 115 +++++++++++++++++++++++++++++-------------
48+ t/cmd/lfs-ssh-echo.go | 33 +++++++++++-
49+ 5 files changed, 162 insertions(+), 74 deletions(-)
50+
51+diff --git a/lfshttp/ssh.go b/lfshttp/ssh.go
52+index d69555ed..ec2cfbab 100644
53+--- a/lfshttp/ssh.go
54++++ b/lfshttp/ssh.go
55+@@ -79,7 +79,7 @@ func (c *sshAuthClient) Resolve(e Endpoint, method string) (sshAuthResponse, err
56+ return res, nil
57+ }
58+
59+- exe, args, _ := ssh.GetLFSExeAndArgs(c.os, c.git, &e.SSHMetadata, "git-lfs-authenticate", endpointOperation(e, method), false)
60++ exe, args, _, _ := ssh.GetLFSExeAndArgs(c.os, c.git, &e.SSHMetadata, "git-lfs-authenticate", endpointOperation(e, method), false, "")
61+ cmd, err := subprocess.ExecCommand(exe, args...)
62+ if err != nil {
63+ return res, err
64+diff --git a/ssh/connection.go b/ssh/connection.go
65+index 45018d1a..b078b3f7 100644
66+--- a/ssh/connection.go
67++++ b/ssh/connection.go
68+@@ -17,10 +17,11 @@ type SSHTransfer struct {
69+ meta *SSHMetadata
70+ operation string
71+ multiplexing bool
72++ controlPath string
73+ }
74+
75+ func NewSSHTransfer(osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, operation string) (*SSHTransfer, error) {
76+- conn, multiplexing, err := startConnection(0, osEnv, gitEnv, meta, operation)
77++ conn, multiplexing, controlPath, err := startConnection(0, osEnv, gitEnv, meta, operation, "")
78+ if err != nil {
79+ return nil, err
80+ }
81+@@ -31,28 +32,29 @@ func NewSSHTransfer(osEnv config.Environment, gitEnv config.Environment, meta *S
82+ meta: meta,
83+ operation: operation,
84+ multiplexing: multiplexing,
85++ controlPath: controlPath,
86+ conn: []*PktlineConnection{conn},
87+ }, nil
88+ }
89+
90+-func startConnection(id int, osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, operation string) (*PktlineConnection, bool, error) {
91++func startConnection(id int, osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, operation string, multiplexControlPath string) (conn *PktlineConnection, multiplexing bool, controlPath string, err error) {
92+ tracerx.Printf("spawning pure SSH connection")
93+- exe, args, multiplexing := GetLFSExeAndArgs(osEnv, gitEnv, meta, "git-lfs-transfer", operation, true)
94++ exe, args, multiplexing, controlPath := GetLFSExeAndArgs(osEnv, gitEnv, meta, "git-lfs-transfer", operation, true, multiplexControlPath)
95+ cmd, err := subprocess.ExecCommand(exe, args...)
96+ if err != nil {
97+- return nil, false, err
98++ return nil, false, "", err
99+ }
100+ r, err := cmd.StdoutPipe()
101+ if err != nil {
102+- return nil, false, err
103++ return nil, false, "", err
104+ }
105+ w, err := cmd.StdinPipe()
106+ if err != nil {
107+- return nil, false, err
108++ return nil, false, "", err
109+ }
110+ err = cmd.Start()
111+ if err != nil {
112+- return nil, false, err
113++ return nil, false, "", err
114+ }
115+
116+ var pl Pktline
117+@@ -61,7 +63,7 @@ func startConnection(id int, osEnv config.Environment, gitEnv config.Environment
118+ } else {
119+ pl = pktline.NewPktline(r, w)
120+ }
121+- conn := &PktlineConnection{
122++ conn = &PktlineConnection{
123+ cmd: cmd,
124+ pl: pl,
125+ r: r,
126+@@ -74,7 +76,7 @@ func startConnection(id int, osEnv config.Environment, gitEnv config.Environment
127+ cmd.Wait()
128+ }
129+ tracerx.Printf("pure SSH connection successful")
130+- return conn, multiplexing, err
131++ return conn, multiplexing, controlPath, err
132+ }
133+
134+ // Connection returns the nth connection (starting from 0) in this transfer
135+@@ -123,22 +125,37 @@ func (tr *SSHTransfer) SetConnectionCountAtLeast(n int) error {
136+ func (tr *SSHTransfer) setConnectionCount(n int) error {
137+ count := len(tr.conn)
138+ if n < count {
139+- for _, item := range tr.conn[n:count] {
140++ tn := n
141++ if tn == 0 {
142++ tn = 1
143++ }
144++ for _, item := range tr.conn[tn:count] {
145+ tracerx.Printf("terminating pure SSH connection (%d -> %d)", count, n)
146+ if err := item.End(); err != nil {
147+ return err
148+ }
149+ }
150+- tr.conn = tr.conn[0:n]
151++ tr.conn = tr.conn[0:tn]
152+ } else if n > count {
153+ for i := count; i < n; i++ {
154+- conn, _, err := startConnection(i, tr.osEnv, tr.gitEnv, tr.meta, tr.operation)
155++ conn, _, controlPath, err := startConnection(i, tr.osEnv, tr.gitEnv, tr.meta, tr.operation, tr.controlPath)
156+ if err != nil {
157+ tracerx.Printf("failed to spawn pure SSH connection: %s", err)
158+ return err
159+ }
160+ tr.conn = append(tr.conn, conn)
161++ if i == 0 {
162++ tr.controlPath = controlPath
163++ }
164++ }
165++ }
166++ if n == 0 && count > 0 {
167++ tracerx.Printf("terminating pure SSH connection (%d -> %d)", count, n)
168++ if err := tr.conn[0].End(); err != nil {
169++ return err
170+ }
171++ tr.conn = nil
172++ tr.controlPath = ""
173+ }
174+ return nil
175+ }
176+diff --git a/ssh/ssh.go b/ssh/ssh.go
177+index b9e9fcd6..b78833ce 100644
178+--- a/ssh/ssh.go
179++++ b/ssh/ssh.go
180+@@ -3,11 +3,11 @@ package ssh
181+ import (
182+ "fmt"
183+ "os"
184++ "path"
185+ "path/filepath"
186+ "regexp"
187+ "runtime"
188+ "strings"
189+- "syscall"
190+
191+ "github.com/git-lfs/git-lfs/v3/config"
192+ "github.com/git-lfs/git-lfs/v3/subprocess"
193+@@ -30,7 +30,7 @@ type SSHMetadata struct {
194+ Path string
195+ }
196+
197+-func FormatArgs(cmd string, args []string, needShell bool, multiplex bool) (string, []string) {
198++func FormatArgs(cmd string, args []string, needShell bool, multiplex bool, controlPath string) (string, []string) {
199+ if !needShell {
200+ return cmd, args
201+ }
202+@@ -38,12 +38,12 @@ func FormatArgs(cmd string, args []string, needShell bool, multiplex bool) (stri
203+ return subprocess.FormatForShellQuotedArgs(cmd, args)
204+ }
205+
206+-func GetLFSExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, command, operation string, multiplexDesired bool) (string, []string, bool) {
207+- exe, args, needShell, multiplexing := GetExeAndArgs(osEnv, gitEnv, meta, multiplexDesired)
208++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) {
209++ exe, args, needShell, multiplexing, controlPath := GetExeAndArgs(osEnv, gitEnv, meta, multiplexDesired, multiplexControlPath)
210+ args = append(args, fmt.Sprintf("%s %s %s", command, meta.Path, operation))
211+- exe, args = FormatArgs(exe, args, needShell, multiplexing)
212++ exe, args = FormatArgs(exe, args, needShell, multiplexing, controlPath)
213+ tracerx.Printf("run_command: %s %s", exe, strings.Join(args, " "))
214+- return exe, args, multiplexing
215++ return exe, args, multiplexing, controlPath
216+ }
217+
218+ // Parse command, and if it looks like a valid command, return the ssh binary
219+@@ -119,22 +119,12 @@ func getControlDir(osEnv config.Environment) (string, error) {
220+ if dir == "" {
221+ return os.MkdirTemp(tmpdir, pattern)
222+ }
223+- dir = filepath.Join(dir, "git-lfs")
224+- err := os.Mkdir(dir, 0700)
225+- if err != nil {
226+- // Ideally we would use errors.Is here to check against
227+- // os.ErrExist, but that's not available on Go 1.11.
228+- perr, ok := err.(*os.PathError)
229+- if !ok || perr.Err != syscall.EEXIST {
230+- return os.MkdirTemp(tmpdir, pattern)
231+- }
232+- }
233+- return dir, nil
234++ return os.MkdirTemp(dir, pattern)
235+ }
236+
237+ // Return the executable name for ssh on this machine and the base args
238+ // Base args includes port settings, user/host, everything pre the command to execute
239+-func GetExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, multiplexDesired bool) (exe string, baseargs []string, needShell bool, multiplexing bool) {
240++func GetExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, multiplexDesired bool, multiplexControlPath string) (exe string, baseargs []string, needShell bool, multiplexing bool, controlPath string) {
241+ var cmd string
242+
243+ ssh, _ := osEnv.Get("GIT_SSH")
244+@@ -161,13 +151,20 @@ func GetExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SS
245+ }
246+
247+ multiplexing = false
248+- multiplexEnabled := gitEnv.Bool("lfs.ssh.automultiplex", true)
249++ multiplexEnabled := gitEnv.Bool("lfs.ssh.automultiplex", runtime.GOOS != "windows")
250+ if variant == variantSSH && multiplexDesired && multiplexEnabled {
251+- controlPath, err := getControlDir(osEnv)
252+- if err == nil {
253++ controlMasterArg := "-oControlMaster=no"
254++ controlPath = multiplexControlPath
255++ if multiplexControlPath == "" {
256++ controlMasterArg = "-oControlMaster=yes"
257++ controlDir, err := getControlDir(osEnv)
258++ if err == nil {
259++ controlPath = path.Join(controlDir, "lfs.sock")
260++ }
261++ }
262++ if controlPath != "" {
263+ multiplexing = true
264+- controlPath = filepath.Join(controlPath, "sock-%C")
265+- args = append(args, "-oControlMaster=auto", fmt.Sprintf("-oControlPath=%s", controlPath))
266++ args = append(args, controlMasterArg, fmt.Sprintf("-oControlPath=%s", controlPath))
267+ }
268+ }
269+
270+@@ -198,7 +195,7 @@ func GetExeAndArgs(osEnv config.Environment, gitEnv config.Environment, meta *SS
271+ args = append(args, meta.UserAndHost)
272+ }
273+
274+- return cmd, args, needShell, multiplexing
275++ return cmd, args, needShell, multiplexing, controlPath
276+ }
277+
278+ const defaultSSHCmd = "ssh"
279+diff --git a/ssh/ssh_test.go b/ssh/ssh_test.go
280+index 2b48025d..420d3f40 100644
281+--- a/ssh/ssh_test.go
282++++ b/ssh/ssh_test.go
283+@@ -20,14 +20,14 @@ func TestSSHGetLFSExeAndArgs(t *testing.T) {
284+ meta.UserAndHost = "user@foo.com"
285+ meta.Path = "user/repo"
286+
287+- exe, args, _ := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, "git-lfs-authenticate", "download", false)
288++ exe, args, _, _ := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, "git-lfs-authenticate", "download", false, "")
289+ assert.Equal(t, "ssh", exe)
290+ assert.Equal(t, []string{
291+ "user@foo.com",
292+ "git-lfs-authenticate user/repo download",
293+ }, args)
294+
295+- exe, args, _ = ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, "git-lfs-authenticate", "upload", false)
296++ exe, args, _, _ = ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, "git-lfs-authenticate", "upload", false, "")
297+ assert.Equal(t, "ssh", exe)
298+ assert.Equal(t, []string{
299+ "user@foo.com",
300+@@ -45,7 +45,7 @@ func TestSSHGetExeAndArgsSsh(t *testing.T) {
301+ meta := ssh.SSHMetadata{}
302+ meta.UserAndHost = "user@foo.com"
303+
304+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
305++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
306+ assert.Equal(t, "ssh", exe)
307+ assert.Equal(t, []string{"user@foo.com"}, args)
308+ }
309+@@ -61,29 +61,72 @@ func TestSSHGetExeAndArgsSshCustomPort(t *testing.T) {
310+ meta.UserAndHost = "user@foo.com"
311+ meta.Port = "8888"
312+
313+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
314++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
315+ assert.Equal(t, "ssh", exe)
316+ assert.Equal(t, []string{"-p", "8888", "user@foo.com"}, args)
317+ }
318+
319+-func TestSSHGetExeAndArgsSshMultiplexing(t *testing.T) {
320++func TestSSHGetExeAndArgsSshNoMultiplexing(t *testing.T) {
321+ cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
322+ "GIT_SSH_COMMAND": "",
323+ "GIT_SSH": "",
324+- }, nil))
325++ }, map[string]string{
326++ "lfs.ssh.automultiplex": "false",
327++ }))
328++ require.Nil(t, err)
329++
330++ meta := ssh.SSHMetadata{}
331++ meta.UserAndHost = "user@foo.com"
332++
333++ exe, baseargs, needShell, multiplexing, controlPath := ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, true, "")
334++ exe, args := ssh.FormatArgs(exe, baseargs, needShell, multiplexing, controlPath)
335++ assert.Equal(t, "ssh", exe)
336++ assert.Equal(t, false, multiplexing)
337++ assert.Equal(t, []string{"user@foo.com"}, args)
338++ assert.Empty(t, controlPath)
339++}
340++
341++func TestSSHGetExeAndArgsSshMultiplexingMaster(t *testing.T) {
342++ cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
343++ "GIT_SSH_COMMAND": "",
344++ "GIT_SSH": "",
345++ }, map[string]string{
346++ "lfs.ssh.automultiplex": "true",
347++ }))
348+ require.Nil(t, err)
349+
350+ meta := ssh.SSHMetadata{}
351+ meta.UserAndHost = "user@foo.com"
352+
353+- exe, baseargs, needShell, multiplexing := ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, true)
354+- exe, args := ssh.FormatArgs(exe, baseargs, needShell, multiplexing)
355++ exe, baseargs, needShell, multiplexing, controlPath := ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, true, "")
356++ exe, args := ssh.FormatArgs(exe, baseargs, needShell, multiplexing, controlPath)
357+ assert.Equal(t, "ssh", exe)
358+- assert.Equal(t, multiplexing, true)
359++ assert.Equal(t, true, multiplexing)
360+ assert.Equal(t, 3, len(args))
361+- assert.Equal(t, "-oControlMaster=auto", args[0])
362++ assert.Equal(t, "-oControlMaster=yes", args[0])
363+ assert.True(t, strings.HasPrefix(args[1], "-oControlPath="))
364+ assert.Equal(t, "user@foo.com", args[2])
365++ assert.NotEmpty(t, controlPath)
366++}
367++
368++func TestSSHGetExeAndArgsSshMultiplexingExtra(t *testing.T) {
369++ cli, err := lfshttp.NewClient(lfshttp.NewContext(nil, map[string]string{
370++ "GIT_SSH_COMMAND": "",
371++ "GIT_SSH": "",
372++ }, map[string]string{
373++ "lfs.ssh.automultiplex": "true",
374++ }))
375++ require.Nil(t, err)
376++
377++ meta := ssh.SSHMetadata{}
378++ meta.UserAndHost = "user@foo.com"
379++
380++ exe, baseargs, needShell, multiplexing, controlPath := ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, true, "/tmp/lfs/lfs.sock")
381++ exe, args := ssh.FormatArgs(exe, baseargs, needShell, multiplexing, controlPath)
382++ assert.Equal(t, "ssh", exe)
383++ assert.Equal(t, true, multiplexing)
384++ assert.Equal(t, []string{"-oControlMaster=no", "-oControlPath=/tmp/lfs/lfs.sock", "user@foo.com"}, args)
385++ assert.Equal(t, "/tmp/lfs/lfs.sock", controlPath)
386+ }
387+
388+ func TestSSHGetExeAndArgsPlink(t *testing.T) {
389+@@ -98,7 +141,7 @@ func TestSSHGetExeAndArgsPlink(t *testing.T) {
390+ meta := ssh.SSHMetadata{}
391+ meta.UserAndHost = "user@foo.com"
392+
393+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
394++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
395+ assert.Equal(t, plink, exe)
396+ assert.Equal(t, []string{"user@foo.com"}, args)
397+ }
398+@@ -116,7 +159,7 @@ func TestSSHGetExeAndArgsPlinkCustomPort(t *testing.T) {
399+ meta.UserAndHost = "user@foo.com"
400+ meta.Port = "8888"
401+
402+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
403++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
404+ assert.Equal(t, plink, exe)
405+ assert.Equal(t, []string{"-P", "8888", "user@foo.com"}, args)
406+ }
407+@@ -135,7 +178,7 @@ func TestSSHGetExeAndArgsPlinkCustomPortExplicitEnvironment(t *testing.T) {
408+ meta.UserAndHost = "user@foo.com"
409+ meta.Port = "8888"
410+
411+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
412++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
413+ assert.Equal(t, plink, exe)
414+ assert.Equal(t, []string{"-P", "8888", "user@foo.com"}, args)
415+ }
416+@@ -154,7 +197,7 @@ func TestSSHGetExeAndArgsPlinkCustomPortExplicitEnvironmentPutty(t *testing.T) {
417+ meta.UserAndHost = "user@foo.com"
418+ meta.Port = "8888"
419+
420+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
421++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
422+ assert.Equal(t, plink, exe)
423+ assert.Equal(t, []string{"-P", "8888", "user@foo.com"}, args)
424+ }
425+@@ -173,7 +216,7 @@ func TestSSHGetExeAndArgsPlinkCustomPortExplicitEnvironmentSsh(t *testing.T) {
426+ meta.UserAndHost = "user@foo.com"
427+ meta.Port = "8888"
428+
429+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
430++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
431+ assert.Equal(t, plink, exe)
432+ assert.Equal(t, []string{"-p", "8888", "user@foo.com"}, args)
433+ }
434+@@ -190,7 +233,7 @@ func TestSSHGetExeAndArgsTortoisePlink(t *testing.T) {
435+ meta := ssh.SSHMetadata{}
436+ meta.UserAndHost = "user@foo.com"
437+
438+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
439++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
440+ assert.Equal(t, plink, exe)
441+ assert.Equal(t, []string{"-batch", "user@foo.com"}, args)
442+ }
443+@@ -208,7 +251,7 @@ func TestSSHGetExeAndArgsTortoisePlinkCustomPort(t *testing.T) {
444+ meta.UserAndHost = "user@foo.com"
445+ meta.Port = "8888"
446+
447+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
448++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
449+ assert.Equal(t, plink, exe)
450+ assert.Equal(t, []string{"-batch", "-P", "8888", "user@foo.com"}, args)
451+ }
452+@@ -227,7 +270,7 @@ func TestSSHGetExeAndArgsTortoisePlinkCustomPortExplicitEnvironment(t *testing.T
453+ meta.UserAndHost = "user@foo.com"
454+ meta.Port = "8888"
455+
456+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
457++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
458+ assert.Equal(t, plink, exe)
459+ assert.Equal(t, []string{"-batch", "-P", "8888", "user@foo.com"}, args)
460+ }
461+@@ -248,7 +291,7 @@ func TestSSHGetExeAndArgsTortoisePlinkCustomPortExplicitConfig(t *testing.T) {
462+ meta.UserAndHost = "user@foo.com"
463+ meta.Port = "8888"
464+
465+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
466++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
467+ assert.Equal(t, plink, exe)
468+ assert.Equal(t, []string{"-batch", "-P", "8888", "user@foo.com"}, args)
469+ }
470+@@ -268,7 +311,7 @@ func TestSSHGetExeAndArgsTortoisePlinkCustomPortExplicitConfigOverride(t *testin
471+ meta.UserAndHost = "user@foo.com"
472+ meta.Port = "8888"
473+
474+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
475++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
476+ assert.Equal(t, plink, exe)
477+ assert.Equal(t, []string{"-P", "8888", "user@foo.com"}, args)
478+ }
479+@@ -284,7 +327,7 @@ func TestSSHGetExeAndArgsSshCommandPrecedence(t *testing.T) {
480+ meta := ssh.SSHMetadata{}
481+ meta.UserAndHost = "user@foo.com"
482+
483+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
484++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
485+ assert.Equal(t, "sh", exe)
486+ assert.Equal(t, []string{"-c", "sshcmd user@foo.com"}, args)
487+ }
488+@@ -299,7 +342,7 @@ func TestSSHGetExeAndArgsSshCommandArgs(t *testing.T) {
489+ meta := ssh.SSHMetadata{}
490+ meta.UserAndHost = "user@foo.com"
491+
492+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
493++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
494+ assert.Equal(t, "sh", exe)
495+ assert.Equal(t, []string{"-c", "sshcmd --args 1 user@foo.com"}, args)
496+ }
497+@@ -314,7 +357,7 @@ func TestSSHGetExeAndArgsSshCommandArgsWithMixedQuotes(t *testing.T) {
498+ meta := ssh.SSHMetadata{}
499+ meta.UserAndHost = "user@foo.com"
500+
501+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
502++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
503+ assert.Equal(t, "sh", exe)
504+ assert.Equal(t, []string{"-c", "sshcmd foo 'bar \"baz\"' user@foo.com"}, args)
505+ }
506+@@ -329,7 +372,7 @@ func TestSSHGetExeAndArgsSshCommandCustomPort(t *testing.T) {
507+ meta.UserAndHost = "user@foo.com"
508+ meta.Port = "8888"
509+
510+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
511++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
512+ assert.Equal(t, "sh", exe)
513+ assert.Equal(t, []string{"-c", "sshcmd -p 8888 user@foo.com"}, args)
514+ }
515+@@ -345,7 +388,7 @@ func TestSSHGetExeAndArgsCoreSshCommand(t *testing.T) {
516+ meta := ssh.SSHMetadata{}
517+ meta.UserAndHost = "user@foo.com"
518+
519+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
520++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
521+ assert.Equal(t, "sh", exe)
522+ assert.Equal(t, []string{"-c", "sshcmd --args 2 user@foo.com"}, args)
523+ }
524+@@ -359,7 +402,7 @@ func TestSSHGetExeAndArgsCoreSshCommandArgsWithMixedQuotes(t *testing.T) {
525+ meta := ssh.SSHMetadata{}
526+ meta.UserAndHost = "user@foo.com"
527+
528+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
529++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
530+ assert.Equal(t, "sh", exe)
531+ assert.Equal(t, []string{"-c", "sshcmd foo 'bar \"baz\"' user@foo.com"}, args)
532+ }
533+@@ -373,7 +416,7 @@ func TestSSHGetExeAndArgsConfigVersusEnv(t *testing.T) {
534+ meta := ssh.SSHMetadata{}
535+ meta.UserAndHost = "user@foo.com"
536+
537+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
538++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
539+ assert.Equal(t, "sh", exe)
540+ assert.Equal(t, []string{"-c", "sshcmd --args 1 user@foo.com"}, args)
541+ }
542+@@ -389,7 +432,7 @@ func TestSSHGetExeAndArgsPlinkCommand(t *testing.T) {
543+ meta := ssh.SSHMetadata{}
544+ meta.UserAndHost = "user@foo.com"
545+
546+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
547++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
548+ assert.Equal(t, "sh", exe)
549+ assert.Equal(t, []string{"-c", plink + " user@foo.com"}, args)
550+ }
551+@@ -406,7 +449,7 @@ func TestSSHGetExeAndArgsPlinkCommandCustomPort(t *testing.T) {
552+ meta.UserAndHost = "user@foo.com"
553+ meta.Port = "8888"
554+
555+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
556++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
557+ assert.Equal(t, "sh", exe)
558+ assert.Equal(t, []string{"-c", plink + " -P 8888 user@foo.com"}, args)
559+ }
560+@@ -422,7 +465,7 @@ func TestSSHGetExeAndArgsTortoisePlinkCommand(t *testing.T) {
561+ meta := ssh.SSHMetadata{}
562+ meta.UserAndHost = "user@foo.com"
563+
564+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
565++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
566+ assert.Equal(t, "sh", exe)
567+ assert.Equal(t, []string{"-c", plink + " -batch user@foo.com"}, args)
568+ }
569+@@ -439,7 +482,7 @@ func TestSSHGetExeAndArgsTortoisePlinkCommandCustomPort(t *testing.T) {
570+ meta.UserAndHost = "user@foo.com"
571+ meta.Port = "8888"
572+
573+- exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false))
574++ exe, args := ssh.FormatArgs(ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &meta, false, ""))
575+ assert.Equal(t, "sh", exe)
576+ assert.Equal(t, []string{"-c", plink + " -batch -P 8888 user@foo.com"}, args)
577+ }
578+@@ -460,7 +503,7 @@ func TestSSHGetLFSExeAndArgsWithCustomSSH(t *testing.T) {
579+ assert.Equal(t, "git@host.com", e.SSHMetadata.UserAndHost)
580+ assert.Equal(t, "/repo", e.SSHMetadata.Path)
581+
582+- exe, args, _ := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "git-lfs-authenticate", "download", false)
583++ exe, args, _, _ := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "git-lfs-authenticate", "download", false, "")
584+ assert.Equal(t, "not-ssh", exe)
585+ assert.Equal(t, []string{"-p", "12345", "git@host.com", "git-lfs-authenticate /repo download"}, args)
586+ }
587+@@ -478,7 +521,7 @@ func TestSSHGetLFSExeAndArgsInvalidOptionsAsHost(t *testing.T) {
588+ assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SSHMetadata.UserAndHost)
589+ assert.Equal(t, "/repo", e.SSHMetadata.Path)
590+
591+- exe, args, _ := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "git-lfs-authenticate", "download", false)
592++ exe, args, _, _ := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "git-lfs-authenticate", "download", false, "")
593+ assert.Equal(t, "ssh", exe)
594+ assert.Equal(t, []string{"--", "-oProxyCommand=gnome-calculator", "git-lfs-authenticate /repo download"}, args)
595+ }
596+@@ -499,7 +542,7 @@ func TestSSHGetLFSExeAndArgsInvalidOptionsAsHostWithCustomSSH(t *testing.T) {
597+ assert.Equal(t, "--oProxyCommand=gnome-calculator", e.SSHMetadata.UserAndHost)
598+ assert.Equal(t, "/repo", e.SSHMetadata.Path)
599+
600+- exe, args, _ := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "git-lfs-authenticate", "download", false)
601++ exe, args, _, _ := ssh.GetLFSExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, "git-lfs-authenticate", "download", false, "")
602+ assert.Equal(t, "not-ssh", exe)
603+ assert.Equal(t, []string{"oProxyCommand=gnome-calculator", "git-lfs-authenticate /repo download"}, args)
604+ }
605+@@ -517,7 +560,7 @@ func TestSSHGetExeAndArgsInvalidOptionsAsHost(t *testing.T) {
606+ assert.Equal(t, "-oProxyCommand=gnome-calculator", e.SSHMetadata.UserAndHost)
607+ assert.Equal(t, "", e.SSHMetadata.Path)
608+
609+- exe, args, needShell, _ := ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, false)
610++ exe, args, needShell, _, _ := ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, false, "")
611+ assert.Equal(t, "ssh", exe)
612+ assert.Equal(t, []string{"--", "-oProxyCommand=gnome-calculator"}, args)
613+ assert.Equal(t, false, needShell)
614+@@ -536,7 +579,7 @@ func TestSSHGetExeAndArgsInvalidOptionsAsPath(t *testing.T) {
615+ assert.Equal(t, "git@git-host.com", e.SSHMetadata.UserAndHost)
616+ assert.Equal(t, "/-oProxyCommand=gnome-calculator", e.SSHMetadata.Path)
617+
618+- exe, args, needShell, _ := ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, false)
619++ exe, args, needShell, _, _ := ssh.GetExeAndArgs(cli.OSEnv(), cli.GitEnv(), &e.SSHMetadata, false, "")
620+ assert.Equal(t, "ssh", exe)
621+ assert.Equal(t, []string{"git@git-host.com"}, args)
622+ assert.Equal(t, false, needShell)
623+diff --git a/t/cmd/lfs-ssh-echo.go b/t/cmd/lfs-ssh-echo.go
624+index f1319bde..b51ce611 100644
625+--- a/t/cmd/lfs-ssh-echo.go
626++++ b/t/cmd/lfs-ssh-echo.go
627+@@ -60,7 +60,38 @@ func main() {
628+ offset := 1
629+
630+ checkSufficientArgs(offset)
631+- if os.Args[offset] == "-oControlMaster=auto" {
632++ if masterArg, found := strings.CutPrefix(os.Args[offset], "-oControlMaster="); found {
633++ var master bool
634++ switch masterArg {
635++ case "yes":
636++ master = true
637++ case "no":
638++ master = false
639++ default:
640++ fmt.Fprintf(os.Stderr, "expected \"-oControlMaster=yes\" or \"-oControlMaster=no\", got %q", os.Args[offset])
641++ os.Exit(1)
642++ }
643++ if pathArg, found := strings.CutPrefix(os.Args[offset+1], "-oControlPath="); found {
644++ if master {
645++ if file, err := os.OpenFile(pathArg, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0); err != nil {
646++ fmt.Fprintf(os.Stderr, "expected %q to not exist", pathArg)
647++ os.Exit(1)
648++ } else {
649++ file.Close()
650++ defer os.Remove(pathArg)
651++ }
652++ } else {
653++ if file, err := os.OpenFile(pathArg, os.O_RDONLY, 0); err != nil {
654++ fmt.Fprintf(os.Stderr, "expected %q to exist", pathArg)
655++ os.Exit(1)
656++ } else {
657++ file.Close()
658++ }
659++ }
660++ } else {
661++ fmt.Fprintf(os.Stderr, "expected \"-oControlPath\"")
662++ os.Exit(1)
663++ }
664+ offset += 2
665+ }
666+
667+--
668+2.52.0
669+
670diff --git a/debian/patches/0002-tq-transfer_test.go-enable-and-fix-all-tests.patch b/debian/patches/0002-tq-transfer_test.go-enable-and-fix-all-tests.patch
671new file mode 100644
672index 0000000..6491781
673--- /dev/null
674+++ b/debian/patches/0002-tq-transfer_test.go-enable-and-fix-all-tests.patch
675@@ -0,0 +1,181 @@
676+From 8b3b2eb4a764747393f1e1c59d571471009005f7 Mon Sep 17 00:00:00 2001
677+From: Chris Darroch <chrisd8088@github.com>
678+Date: Wed, 26 Jul 2023 17:20:23 -0700
679+Subject: [PATCH 2/3] tq/transfer_test.go: enable and fix all tests
680+
681+The three test functions in the tq/transfer_test.go source file are
682+all named with the prefix "test" rather than "Test", and as a result,
683+do not actually execute. This oversight dates from the original
684+introduction of these tests in the "transfer" package in commit
685+10623f5d7a7d785fb3863c2b190b0f2f99686b7c of PR #1265. (The package
686+was later renamed to the current "tq" package in commit
687+891db97a429405f80d54c7e09219bda9799e8e6e of PR #1780.)
688+
689+We therefore change the test function names to begin with "Test",
690+and resolve several test regressions which have accumulated since the
691+tests were first added.
692+
693+First, the TestBasicAdapterExists() function calls the
694+GetDownloadAdapterNames() and GetUploadAdapterNames() methods of the
695+Manifest structure, and these now return the names of three transfer
696+adapter implementations rather than just the original "basic" one,
697+so we allow for all three names to appear in any order. (The
698+"lfs-standalone-file" adapter was added in commit
699+bb05cf5053d9abd6d7ca68354ca9663e57bb6737 of PR #3748, and the "ssh"
700+adapter was added in commit 594f8e386cce3441e06c9094ab5e251f0e07ca1f
701+of PR #4446.)
702+
703+Second, the TestAdapterRegAndOverride() function expects the
704+NewDownloadAdapter() and NewUploadAdapter() methods of the Manifest
705+structure to return nil if the provided name argument does not match
706+that of any registered transfer adapter. However, this has not been
707+the behaviour of those methods since commit
708+c5c2a756c70b2a961cea284fc7b00d2753bfaa3b of PR #1279, shortly after
709+the tests were first introduced in PR #1265. In that commit, the
710+NewAdapterOrDefault() method was added, and the NewDownloadAdapter()
711+and NewUploadAdapter() methods revised to call it, so they return the
712+default "basic" adapter if the requested name does not match a
713+registered adapter. We therefore revise and expand the test to
714+account for this behaviour, and also make sure to directly test the
715+underlying NewAdapter() method, which retains the originally intended
716+behaviour and returns nil if it does not find a matching adapter
717+for the provided name argument.
718+
719+Third, although the TestAdapterRegButBasicOnly() function passes without
720+changes, it no longer fully performs the checks it was intended to make,
721+since the NewDownloadAdapter() and NewUploadAdapter() methods now always
722+return a non-nil value, so using a non-nil response from them to prove
723+that the "test" adapter was found is insufficient. We therefore update
724+the test to confirm that the returned value from these functions is
725+a "test" adapter, as expected, and not just a "basic" one.
726+
727+We also replace the use of the BasicAdapterName variable with the
728+"basic" string to align with the other tests.
729+---
730+ tq/transfer_test.go | 66 ++++++++++++++++++++++++++++++++++-----------
731+ 1 file changed, 51 insertions(+), 15 deletions(-)
732+
733+diff --git a/tq/transfer_test.go b/tq/transfer_test.go
734+index 3a3e1b0e..6b73fce3 100644
735+--- a/tq/transfer_test.go
736++++ b/tq/transfer_test.go
737+@@ -41,18 +41,18 @@ func newRenamedTestAdapter(name string, dir Direction) Adapter {
738+ return &testAdapter{"RENAMED", dir}
739+ }
740+
741+-func testBasicAdapterExists(t *testing.T) {
742++func TestBasicAdapterExists(t *testing.T) {
743+ m := NewManifest(nil, nil, "", "")
744+
745+ assert := assert.New(t)
746+
747+ dls := m.GetDownloadAdapterNames()
748+ if assert.NotNil(dls) {
749+- assert.Equal([]string{"basic"}, dls)
750++ assert.ElementsMatch([]string{"basic", "lfs-standalone-file", "ssh"}, dls)
751+ }
752+ uls := m.GetUploadAdapterNames()
753+ if assert.NotNil(uls) {
754+- assert.Equal([]string{"basic"}, uls)
755++ assert.ElementsMatch([]string{"basic", "lfs-standalone-file", "ssh"}, dls)
756+ }
757+
758+ da := m.NewDownloadAdapter("basic")
759+@@ -68,25 +68,52 @@ func testBasicAdapterExists(t *testing.T) {
760+ }
761+ }
762+
763+-func testAdapterRegAndOverride(t *testing.T) {
764++func TestAdapterRegAndOverride(t *testing.T) {
765+ m := NewManifest(nil, nil, "", "")
766+ assert := assert.New(t)
767+
768+- assert.Nil(m.NewDownloadAdapter("test"))
769+- assert.Nil(m.NewUploadAdapter("test"))
770++ assert.Nil(m.NewAdapter("test", Download))
771++ assert.Nil(m.NewAdapter("test", Upload))
772++
773++ da := m.NewDownloadAdapter("test")
774++ if assert.NotNil(da) {
775++ assert.Equal("basic", da.Name())
776++ assert.Equal(Download, da.Direction())
777++ }
778++
779++ ua := m.NewUploadAdapter("test")
780++ if assert.NotNil(ua) {
781++ assert.Equal("basic", ua.Name())
782++ assert.Equal(Upload, ua.Direction())
783++ }
784+
785+ m.RegisterNewAdapterFunc("test", Upload, newTestAdapter)
786+- assert.Nil(m.NewDownloadAdapter("test"))
787+- assert.NotNil(m.NewUploadAdapter("test"))
788++ assert.Nil(m.NewAdapter("test", Download))
789++ assert.NotNil(m.NewAdapter("test", Upload))
790++
791++ da = m.NewDownloadAdapter("test")
792++ if assert.NotNil(da) {
793++ assert.Equal("basic", da.Name())
794++ assert.Equal(Download, da.Direction())
795++ }
796++
797++ ua = m.NewUploadAdapter("test")
798++ if assert.NotNil(ua) {
799++ assert.Equal("test", ua.Name())
800++ assert.Equal(Upload, ua.Direction())
801++ }
802+
803+ m.RegisterNewAdapterFunc("test", Download, newTestAdapter)
804+- da := m.NewDownloadAdapter("test")
805++ assert.NotNil(m.NewAdapter("test", Download))
806++ assert.NotNil(m.NewAdapter("test", Upload))
807++
808++ da = m.NewDownloadAdapter("test")
809+ if assert.NotNil(da) {
810+ assert.Equal("test", da.Name())
811+ assert.Equal(Download, da.Direction())
812+ }
813+
814+- ua := m.NewUploadAdapter("test")
815++ ua = m.NewUploadAdapter("test")
816+ if assert.NotNil(ua) {
817+ assert.Equal("test", ua.Name())
818+ assert.Equal(Upload, ua.Direction())
819+@@ -114,7 +141,7 @@ func testAdapterRegAndOverride(t *testing.T) {
820+ }
821+ }
822+
823+-func testAdapterRegButBasicOnly(t *testing.T) {
824++func TestAdapterRegButBasicOnly(t *testing.T) {
825+ cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
826+ "lfs.basictransfersonly": "yes",
827+ }))
828+@@ -127,12 +154,21 @@ func testAdapterRegButBasicOnly(t *testing.T) {
829+ m.RegisterNewAdapterFunc("test", Upload, newTestAdapter)
830+ m.RegisterNewAdapterFunc("test", Download, newTestAdapter)
831+ // Will still be created if we ask for them
832+- assert.NotNil(m.NewUploadAdapter("test"))
833+- assert.NotNil(m.NewDownloadAdapter("test"))
834++ da := m.NewDownloadAdapter("test")
835++ if assert.NotNil(da) {
836++ assert.Equal("test", da.Name())
837++ assert.Equal(Download, da.Direction())
838++ }
839++
840++ ua := m.NewUploadAdapter("test")
841++ if assert.NotNil(ua) {
842++ assert.Equal("test", ua.Name())
843++ assert.Equal(Upload, ua.Direction())
844++ }
845+
846+ // But list will exclude
847+ ld := m.GetDownloadAdapterNames()
848+- assert.Equal([]string{BasicAdapterName}, ld)
849++ assert.Equal([]string{"basic"}, ld)
850+ lu := m.GetUploadAdapterNames()
851+- assert.Equal([]string{BasicAdapterName}, lu)
852++ assert.Equal([]string{"basic"}, lu)
853+ }
854+--
855+2.52.0
856+
857diff --git a/debian/patches/0003-tq-transfer-copy-Id-and-Token.patch b/debian/patches/0003-tq-transfer-copy-Id-and-Token.patch
858new file mode 100644
859index 0000000..f20e5ab
860--- /dev/null
861+++ b/debian/patches/0003-tq-transfer-copy-Id-and-Token.patch
862@@ -0,0 +1,37 @@
863+From d5c3fd3115e2a51fdd83fd22b01a45c81f36f490 Mon Sep 17 00:00:00 2001
864+From: Kyle Edwards <kyle.edwards@kitware.com>
865+Date: Mon, 2 Oct 2023 16:22:02 -0400
866+Subject: [PATCH 3/3] tq/transfer: copy Id and Token
867+
868+SSH connections that return id and/or token from batch require these
869+fields for get-object and put-object. Properly copy them when making
870+a copy of a transfer in newTransfer().
871+---
872+ tq/transfer.go | 4 ++++
873+ 1 file changed, 4 insertions(+)
874+
875+diff --git a/tq/transfer.go b/tq/transfer.go
876+index 22fb832d..dbb4b896 100644
877+--- a/tq/transfer.go
878++++ b/tq/transfer.go
879+@@ -109,6 +109,8 @@ func newTransfer(tr *Transfer, name string, path string) *Transfer {
880+ Header: action.Header,
881+ ExpiresAt: action.ExpiresAt,
882+ ExpiresIn: action.ExpiresIn,
883++ Id: action.Id,
884++ Token: action.Token,
885+ createdAt: action.createdAt,
886+ }
887+ }
888+@@ -122,6 +124,8 @@ func newTransfer(tr *Transfer, name string, path string) *Transfer {
889+ Header: link.Header,
890+ ExpiresAt: link.ExpiresAt,
891+ ExpiresIn: link.ExpiresIn,
892++ Id: link.Id,
893++ Token: link.Token,
894+ createdAt: link.createdAt,
895+ }
896+ }
897+--
898+2.52.0
899+
900diff --git a/debian/patches/series b/debian/patches/series
901index e69de29..0605dc1 100644
902--- a/debian/patches/series
903+++ b/debian/patches/series
904@@ -0,0 +1,3 @@
905+0001-ssh-Specifically-designate-a-master-multiplex-connec.patch
906+0002-tq-transfer_test.go-enable-and-fix-all-tests.patch
907+0003-tq-transfer-copy-Id-and-Token.patch
908\ No newline at end of file

Subscribers

People subscribed via source and target branches

to all changes: