Repository: go-git/go-git

Issue:
fatal: protocol error: bad line length character: PACK
I'm getting a weird bug that I think is a bug in go-git.

On the client-side I'm seeing the following:

```
$ git pull -vvv --rebase
Server version is go-git/5.x
Server supports ofs-delta
want 526e79c8c4037c2fcbee50acc2d9be1992b60893 (refs/heads/main)
have 51ea2dea8b05b8e631928a990f16fe7566e3c96f
have 6901305097043b4a25e2e20bafab359e635fc476
have 5b5fab934eef3b3957fe5d712672487e9234d0ce
have 2f572924460cb3bcc140cb1b30fab1619bad9b5a
have b701bbb46c14441442376191fcd9476a0ede3120
have 070676a07101c3ddf22bc3169980e0c5068295c0
have f1b049c1238065e2b4908d8526a595dcc5e0a811
have 26fbee7ff623814fb37568c72c89ee515bfb7d8e
have 44101b43124b4eba0308dbeef85bd77b48230d93
have dae95318ea4236d3d157999b7ca2654edb06af36
have 6a7fff648f7922355a6fb965bf9b7c17d939d5e3
have c5bbbf3b1813827981a1746ad450afb10910c203
have f8d8ff5de51ba02b28a52b55ca0523b231ff8acd
have 6f8b2b27f5c3d35dfb41a5a38a3795d8c4962692
have 65d71b41dd0a5f531e9ca577ab11af88762a4e78
have 7d7bf6318f7470b17204767364ff22ff7ad2620d
POST git-upload-pack (903 bytes)
have 5fc5fd9ba13b98582cb2b7ae2b39f28ed7f127a0
have e99894102fc0c98583e35b42ffc2d578dc9a1c1c
have a0f95b37c000e3bda2af160f9ae24cbcc4def343
have 30b8f348eaf906caf0c32bba15b6b8868245d91f
have 5d4b79d0a19658b41ddbe2454f8699d841beb52f
have 99a6559b96f6fe976a5d5cd7b2f396d29492c8b9
have 74fcb64cc38fd5cc4d33c419e29c460f5be6f615
have 983dabb33c2dddcdba65b8ab9d0922bd338628aa
have 30559cd9c23ca4e803364ba1848442d217560143
have f661e6e4ce1177352b5bc40238351920cc25a69b
have 2b57b6e0b1b936f45aa4ed8fd0ed2f57b7d370f3
have 2fe14eca63e9d3e907c7b725407c146a82cc129f
have d58151dbb07a5d5dd7eaf6fc9f12566898823a46
have a64f9d8f14762e341a851d0e211814958e044a64
have d6b8143f569c95bda9a1e5a494affed26b058ce6
have 6e6648e344c889767240a33f42febf9498250131
fatal: protocol error: bad line length character: PACK
```

On the server-side the request/context is cancelled (probably becuase the client bailed above):

```
time="2022-12-29T14:59:47Z" level=error msg="error encoding upload-pack response" func=gitxt.net/gitxt/internal/git.UploadPackHandler.func1 file="/src/internal/git/handler.go:243" error="write tcp 10.0.2.216:5555->10.0.2.163:42390: write: broken pipe"
2022/12/29 14:59:47 http: superfluous response.WriteHeader call from gitxt.net/gitxt/internal/git.UploadPackHandler.func1 (handler.go:244)
```

I'm really not sure what's going wrong here 🤔

Discussion:
@prologic: Die some digging into this and this is what's happening on the wire:

```
C: GET /gitxt/info/refs?service=git-upload-pack HTTP/1.1
Host: localhost:5555
User-Agent: git/2.37.1 (Apple Git-137.1)
Accept: */*
Accept-Encoding: deflate, gzip
Proxy-Connection: Keep-Alive
Pragma: no-cache
Git-Protocol: version=2
content-length: 0

S: HTTP/1.1 200 OK
Content-Type: application/x-git-upload-pack-advertisement
Date: Thu, 29 Dec 2022 16:29:15 GMT
Content-Length: 336

001e# service=git-upload-pack
00000071a67c6daa52a3d45b5942e538e2986a44f931ba64 HEADagent=go-git/5.x ofs-delta symref=HEAD:refs/heads/main no-thin
003da67c6daa52a3d45b5942e538e2986a44f931ba64 refs/heads/main
003ea2802d9a1f443c70c76abcee7982c17c67f65ff4 refs/tags/v0.1.0
003ed6cbb0ee6c7f95a353a183c919d22428fc1ef31b refs/tags/v0.2.0
0000

C: POST /gitxt/git-upload-pack HTTP/1.1
Host: localhost:5555
User-Agent: git/2.37.1 (Apple Git-137.1)
Accept-Encoding: deflate, gzip
Proxy-Connection: Keep-Alive
Content-Type: application/x-git-upload-pack-request
Accept: application/x-git-upload-pack-result
Content-Length: 903

005fwant a67c6daa52a3d45b5942e538e2986a44f931ba64 ofs-delta agent=git/2.37.1.(Apple.Git-137.1)
00000032have 526e79c8c4037c2fcbee50acc2d9be1992b60893
0032have 5b5fab934eef3b3957fe5d712672487e9234d0ce
0032have 2f572924460cb3bcc140cb1b30fab1619bad9b5a
0032have b701bbb46c14441442376191fcd9476a0ede3120
0032have 070676a07101c3ddf22bc3169980e0c5068295c0
0032have f1b049c1238065e2b4908d8526a595dcc5e0a811
0032have 26fbee7ff623814fb37568c72c89ee515bfb7d8e
0032have 44101b43124b4eba0308dbeef85bd77b48230d93
0032have dae95318ea4236d3d157999b7ca2654edb06af36
0032have 6a7fff648f7922355a6fb965bf9b7c17d939d5e3
0032have c5bbbf3b1813827981a1746ad450afb10910c203
0032have f8d8ff5de51ba02b28a52b55ca0523b231ff8acd
0032have 6f8b2b27f5c3d35dfb41a5a38a3795d8c4962692
0032have 65d71b41dd0a5f531e9ca577ab11af88762a4e78
0032have 7d7bf6318f7470b17204767364ff22ff7ad2620d
0032have 5fc5fd9ba13b98582cb2b7ae2b39f28ed7f127a0
0000

S: HTTP/1.1 200 OK
Content-Type: application/x-git-upload-pack-result
Date: Thu, 29 Dec 2022 16:29:15 GMT
Transfer-Encoding: chunked

204c0
0008NAK
PACKp\xb0\x9fx\x9c\xb4\xbbI\x93\x9bJ׮=\xff~Ş+d2\x92\xe6\x8b8@
...
```

I've truncated the final server response as its ~130kB (let me know if you'd like the full response)

The server response `git` seems to be b0rking at starts like this:

```
0000000000 30 30 30 38 4e 41 4b 0a 50 41 43 4b 00 00 00 02   0008NAK.PACK....
0000000010 00 00 02 70 b0 9f 07 78 9c b4 bb 49 93 9b 4a d7   ...p...x...I..J.
0000000020 ae 3d ff 7e c5 9e 2b 64 32 13 92 e6 8b 38 03 40   .=.~..+d2....8.@
0000000030 0d 42 02 24 84 90 d0 e4 04 7d df b7 e2 d7 9f a8   .B.$.....}......
```@prologic: Some more useful information (discovered how to convince `git` to debug packets):

```
Fri Dec 30 15:14:48
~/tmp/gitxt
 (main) 0
$ GIT_TRACE=true GIT_TRACE_PACKET=true git pull --rebase
15:15:00.118967 exec-cmd.c:139          trace: resolved executable path from Darwin stack: /Library/Developer/CommandLineTools/usr/bin/git
15:15:00.119408 exec-cmd.c:238          trace: resolved executable dir: /Library/Developer/CommandLineTools/usr/bin
15:15:00.119917 git.c:460               trace: built-in: git pull --rebase
15:15:00.124015 run-command.c:654       trace: run_command: git fetch --update-head-ok
15:15:00.127583 exec-cmd.c:139          trace: resolved executable path from Darwin stack: /Library/Developer/CommandLineTools/usr/libexec/git-core/git
15:15:00.127988 exec-cmd.c:238          trace: resolved executable dir: /Library/Developer/CommandLineTools/usr/libexec/git-core
15:15:00.128398 git.c:460               trace: built-in: git fetch --update-head-ok
15:15:00.129112 run-command.c:654       trace: run_command: GIT_DIR=.git git remote-http origin http://localhost:5555/gitxt
15:15:00.132303 exec-cmd.c:139          trace: resolved executable path from Darwin stack: /Library/Developer/CommandLineTools/usr/libexec/git-core/git
15:15:00.132628 exec-cmd.c:238          trace: resolved executable dir: /Library/Developer/CommandLineTools/usr/libexec/git-core
15:15:00.132908 git.c:749               trace: exec: git-remote-http origin http://localhost:5555/gitxt
15:15:00.132915 run-command.c:654       trace: run_command: git-remote-http origin http://localhost:5555/gitxt
15:15:00.137006 exec-cmd.c:139          trace: resolved executable path from Darwin stack: /Library/Developer/CommandLineTools/usr/libexec/git-core/git-remote-http
15:15:00.137341 exec-cmd.c:238          trace: resolved executable dir: /Library/Developer/CommandLineTools/usr/libexec/git-core
15:15:00.143922 pkt-line.c:80           packet:          git< # service=git-upload-pack
15:15:00.143928 pkt-line.c:80           packet:          git< 0000
15:15:00.143933 pkt-line.c:80           packet:          git< a67c6daa52a3d45b5942e538e2986a44f931ba64 HEAD\0agent=go-git/5.x ofs-delta symref=HEAD:refs/heads/main no-thin
15:15:00.143939 pkt-line.c:80           packet:          git< a67c6daa52a3d45b5942e538e2986a44f931ba64 refs/heads/main
15:15:00.143941 pkt-line.c:80           packet:          git< a2802d9a1f443c70c76abcee7982c17c67f65ff4 refs/tags/v0.1.0
15:15:00.143943 pkt-line.c:80           packet:          git< d6cbb0ee6c7f95a353a183c919d22428fc1ef31b refs/tags/v0.2.0
15:15:00.143945 pkt-line.c:80           packet:          git< 0000
15:15:00.144222 pkt-line.c:80           packet:          git> a67c6daa52a3d45b5942e538e2986a44f931ba64 refs/heads/main
15:15:00.144228 pkt-line.c:80           packet:          git> 0000
15:15:00.144236 run-command.c:654       trace: run_command: git fetch-pack --stateless-rpc --stdin --lock-pack --include-tag --thin http://localhost:5555/gitxt/
15:15:00.147284 exec-cmd.c:139          trace: resolved executable path from Darwin stack: /Library/Developer/CommandLineTools/usr/libexec/git-core/git
15:15:00.147629 exec-cmd.c:238          trace: resolved executable dir: /Library/Developer/CommandLineTools/usr/libexec/git-core
15:15:00.147986 git.c:460               trace: built-in: git fetch-pack --stateless-rpc --stdin --lock-pack --include-tag --thin http://localhost:5555/gitxt/
15:15:00.148007 pkt-line.c:80           packet:   fetch-pack< a67c6daa52a3d45b5942e538e2986a44f931ba64 refs/heads/main
15:15:00.148013 pkt-line.c:80           packet:   fetch-pack< 0000
15:15:00.148016 pkt-line.c:80           packet:   fetch-pack< a67c6daa52a3d45b5942e538e2986a44f931ba64 HEAD\0agent=go-git/5.x ofs-delta symref=HEAD:refs/heads/main no-thin
15:15:00.148024 pkt-line.c:80           packet:   fetch-pack< a67c6daa52a3d45b5942e538e2986a44f931ba64 refs/heads/main
15:15:00.148027 pkt-line.c:80           packet:   fetch-pack< a2802d9a1f443c70c76abcee7982c17c67f65ff4 refs/tags/v0.1.0
15:15:00.148029 pkt-line.c:80           packet:   fetch-pack< d6cbb0ee6c7f95a353a183c919d22428fc1ef31b refs/tags/v0.2.0
15:15:00.148031 pkt-line.c:80           packet:   fetch-pack< 0000
15:15:00.148455 pkt-line.c:80           packet:   fetch-pack> want a67c6daa52a3d45b5942e538e2986a44f931ba64 ofs-delta agent=git/2.37.1.(Apple.Git-137.1)
15:15:00.148459 pkt-line.c:80           packet:   fetch-pack> 0000
15:15:00.148576 pkt-line.c:80           packet:   fetch-pack> have 526e79c8c4037c2fcbee50acc2d9be1992b60893
15:15:00.148590 pkt-line.c:80           packet:   fetch-pack> have 5b5fab934eef3b3957fe5d712672487e9234d0ce
15:15:00.148601 pkt-line.c:80           packet:   fetch-pack> have 2f572924460cb3bcc140cb1b30fab1619bad9b5a
15:15:00.148644 pkt-line.c:80           packet:   fetch-pack> have b701bbb46c14441442376191fcd9476a0ede3120
15:15:00.148656 pkt-line.c:80           packet:   fetch-pack> have 070676a07101c3ddf22bc3169980e0c5068295c0
15:15:00.148666 pkt-line.c:80           packet:   fetch-pack> have f1b049c1238065e2b4908d8526a595dcc5e0a811
15:15:00.148680 pkt-line.c:80           packet:   fetch-pack> have 26fbee7ff623814fb37568c72c89ee515bfb7d8e
15:15:00.148690 pkt-line.c:80           packet:   fetch-pack> have 44101b43124b4eba0308dbeef85bd77b48230d93
15:15:00.148701 pkt-line.c:80           packet:   fetch-pack> have dae95318ea4236d3d157999b7ca2654edb06af36
15:15:00.148711 pkt-line.c:80           packet:   fetch-pack> have 6a7fff648f7922355a6fb965bf9b7c17d939d5e3
15:15:00.148721 pkt-line.c:80           packet:   fetch-pack> have c5bbbf3b1813827981a1746ad450afb10910c203
15:15:00.148731 pkt-line.c:80           packet:   fetch-pack> have f8d8ff5de51ba02b28a52b55ca0523b231ff8acd
15:15:00.148741 pkt-line.c:80           packet:   fetch-pack> have 6f8b2b27f5c3d35dfb41a5a38a3795d8c4962692
15:15:00.148751 pkt-line.c:80           packet:   fetch-pack> have 65d71b41dd0a5f531e9ca577ab11af88762a4e78
15:15:00.149547 pkt-line.c:80           packet:   fetch-pack> have 7d7bf6318f7470b17204767364ff22ff7ad2620d
15:15:00.149559 pkt-line.c:80           packet:   fetch-pack> have 5fc5fd9ba13b98582cb2b7ae2b39f28ed7f127a0
15:15:00.149561 pkt-line.c:80           packet:   fetch-pack> 0000
15:15:00.149565 pkt-line.c:80           packet:   fetch-pack> 0000
15:15:00.149581 pkt-line.c:80           packet:          git< 005fwant a67c6daa52a3d45b5942e538e2986a44f931ba64 ofs-delta agent=git/2.37.1.(Apple.Git-137.1)00000032have 526e79c8c4037c2fcbee50acc2d9be1992b608930032have 5b5fab934eef3b3957fe5d712672487e9234d0ce0032have 2f572924460cb3bcc140cb1b30fab1619bad9b5a0032have b701bbb46c14441442376191fcd9476a0ede31200032have 070676a07101c3ddf22bc3169980e0c5068295c00032have f1b049c1238065e2b4908d8526a595dcc5e0a8110032have 26fbee7ff623814fb37568c72c89ee515bfb7d8e0032have 44101b43124b4eba0308dbeef85bd77b48230d930032have dae95318ea4236d3d157999b7ca2654edb06af360032have 6a7fff648f7922355a6fb965bf9b7c17d939d5e30032have c5bbbf3b1813827981a1746ad450afb10910c2030032have f8d8ff5de51ba02b28a52b55ca0523b231ff8acd0032have 6f8b2b27f5c3d35dfb41a5a38a3795d8c49626920032have 65d71b41dd0a5f531e9ca577ab11af88762a4e780032have 7d7bf6318f7470b17204767364ff22ff7ad2620d0032have 5fc5fd9ba13b98582cb2b7ae2b39f28ed7f127a00000
15:15:00.149602 pkt-line.c:80           packet:          git< 0000
15:15:00.245807 pkt-line.c:80           packet:   fetch-pack< NAK
15:15:00.245854 pkt-line.c:80           packet:   fetch-pack> have e99894102fc0c98583e35b42ffc2d578dc9a1c1c
15:15:00.245866 pkt-line.c:80           packet:   fetch-pack> have a0f95b37c000e3bda2af160f9ae24cbcc4def343
15:15:00.246156 pkt-line.c:80           packet:   fetch-pack> have 30b8f348eaf906caf0c32bba15b6b8868245d91f
15:15:00.246180 pkt-line.c:80           packet:   fetch-pack> have 5d4b79d0a19658b41ddbe2454f8699d841beb52f
15:15:00.246190 pkt-line.c:80           packet:   fetch-pack> have 99a6559b96f6fe976a5d5cd7b2f396d29492c8b9
15:15:00.246199 pkt-line.c:80           packet:   fetch-pack> have 74fcb64cc38fd5cc4d33c419e29c460f5be6f615
15:15:00.246208 pkt-line.c:80           packet:   fetch-pack> have 983dabb33c2dddcdba65b8ab9d0922bd338628aa
15:15:00.246217 pkt-line.c:80           packet:   fetch-pack> have 30559cd9c23ca4e803364ba1848442d217560143
15:15:00.246225 pkt-line.c:80           packet:   fetch-pack> have f661e6e4ce1177352b5bc40238351920cc25a69b
15:15:00.246233 pkt-line.c:80           packet:   fetch-pack> have 2b57b6e0b1b936f45aa4ed8fd0ed2f57b7d370f3
15:15:00.246242 pkt-line.c:80           packet:   fetch-pack> have 2fe14eca63e9d3e907c7b725407c146a82cc129f
15:15:00.246250 pkt-line.c:80           packet:   fetch-pack> have d58151dbb07a5d5dd7eaf6fc9f12566898823a46
15:15:00.246258 pkt-line.c:80           packet:   fetch-pack> have a64f9d8f14762e341a851d0e211814958e044a64
15:15:00.246267 pkt-line.c:80           packet:   fetch-pack> have d6b8143f569c95bda9a1e5a494affed26b058ce6
15:15:00.246276 pkt-line.c:80           packet:   fetch-pack> have 6e6648e344c889767240a33f42febf9498250131
15:15:00.246284 pkt-line.c:80           packet:   fetch-pack> have 0ed4b29fe0aa3e93557569c6f803eafc5e228a37
15:15:00.246288 pkt-line.c:80           packet:   fetch-pack> 0000
15:15:00.246291 pkt-line.c:80           packet:   fetch-pack> 0000
fatal: protocol error: bad line length character: PACK

```@prologic: @mcarmonaa / @pjbgf Happy New Year 🥳 Happy #2023 😅 -- Would either of you have time soon™ to help me out with this one? 🙏@prologic: ping@blmayer: Same error here.@gl-yziquel: Same error here. Happening with the legit server from icyphox. It's a regression introduced between versions 0.2.0 and 0.2.1 of legit, when it switched from git to go-git.

Probably the same issue that brought @prologic here.@gl-yziquel: @prologic I created a fork of legit, pinned to the 0.2.0 version, in order to advertise this issue.

Here: https://github.com/gl-yziquel/legit@prologic: @gl-yziquel I forked legit long ago and also wanted to switch to using go-git in [gittxt](https://gitxt.net/gitxt); sadly I was never able to solve this particular issue, so gitxt stalled as a PoC/concept.@gl-yziquel: @prologic That issue has been bogging me down for quite some time with legit. I like the bare bones aspect of legit. However, if one is not a native go speaker, it's hard as hell to debug. If I find time, I'll debug it. For the moment, I'll stick with the 0.2.0 version, and perhaps will rebase the codebase leaving out go-git because of this issue. The issue tracer on legit has been rather unresponsive, unfortunately. Thank you for mentioning your fork. Fetching it now.

Edit: yeah. frustrating: you can clone your gittxt repo, but not fetch it...@github-actions: To help us keep things tidy and focus on the active tasks, we've introduced a stale bot to spot issues/PRs that haven't had any activity in a while.

This particular issue hasn't had any updates or activity in the past 90 days, so it's been labeled as 'stale'. If it remains inactive for the next 30 days, it'll be automatically closed.

We understand everyone's busy, but if this issue is still important to you, please feel free to add a comment or make an update to keep it active. 

Thanks for your understanding and cooperation!@runxiyu: It seems that I could reliably reproduce this in some repos, although I couldn't tell what exactly is making them reproducible.
Either this same bug, or a very similar one, is present in 5.13.2.@runxiyu: I see that there's a `v6-transport` branch, but its API is a bit confusing and I don't see relevant examples. Does that branch have fixes for this issue by any means?@pjbgf: @runxiyu the `v6-transport` is still WIP, so documentation and examples are yet to be updated. However, this should be fixed there.

cc: @aymanbagabas in case you have any insights on this.@runxiyu: Ty, I'll work around this by execing git-receive-pack and friends for now, but I'll check back later when v6-transport gets updates



Solution patch:
diff --git a/plumbing/transport/upload_pack.go b/plumbing/transport/upload_pack.go
index 75f69643..17e8eda5 100644
--- a/plumbing/transport/upload_pack.go
+++ b/plumbing/transport/upload_pack.go
@@ -254,14 +254,12 @@ func UploadPack(
 		useSideband bool
 		writer      io.Writer = w
 	)
-	if !caps.Supports(capability.NoProgress) {
-		if caps.Supports(capability.Sideband64k) {
-			writer = sideband.NewMuxer(sideband.Sideband64k, w)
-			useSideband = true
-		} else if caps.Supports(capability.Sideband) {
-			writer = sideband.NewMuxer(sideband.Sideband, w)
-			useSideband = true
-		}
+	if caps.Supports(capability.Sideband64k) {
+		writer = sideband.NewMuxer(sideband.Sideband64k, w)
+		useSideband = true
+	} else if caps.Supports(capability.Sideband) {
+		writer = sideband.NewMuxer(sideband.Sideband, w)
+		useSideband = true
 	}
 
 	// TODO: Support shallow-file



Test patch:
diff --git a/plumbing/transport/serve_test.go b/plumbing/transport/serve_test.go
index 42e497ee..e3ce411c 100644
--- a/plumbing/transport/serve_test.go
+++ b/plumbing/transport/serve_test.go
@@ -14,8 +14,9 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func testAdvertise[T UploadPackOptions | ReceivePackOptions](
+func testServe[T UploadPackOptions | ReceivePackOptions](
 	t testing.TB,
+	st storage.Storer,
 	fun func(
 		ctx context.Context,
 		st storage.Storer,
@@ -23,24 +24,39 @@ func testAdvertise[T UploadPackOptions | ReceivePackOptions](
 		w io.WriteCloser,
 		opts *T,
 	) error,
-	proto string,
-	stateless bool,
+	r io.ReadCloser,
+	opts *T,
 ) *bytes.Buffer {
 	var out bytes.Buffer
-	dot := fixtures.Basic().One().DotGit(fixtures.WithTargetDir(t.TempDir))
-	st := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
 	err := fun(
 		context.TODO(),
 		st,
-		io.NopCloser(bytes.NewBuffer(nil)),
+		r,
 		ioutil.WriteNopCloser(&out),
-		&T{
-			GitProtocol:   proto,
-			AdvertiseRefs: true,
-			StatelessRPC:  stateless,
-		},
+		opts,
 	)
 	require.NoError(t, err)
 	require.Greater(t, out.Len(), 0)
 	return &out
 }
+
+func testAdvertise[T UploadPackOptions | ReceivePackOptions](
+	t testing.TB,
+	fun func(
+		ctx context.Context,
+		st storage.Storer,
+		r io.ReadCloser,
+		w io.WriteCloser,
+		opts *T,
+	) error,
+	proto string,
+	stateless bool,
+) *bytes.Buffer {
+	dot := fixtures.Basic().One().DotGit(fixtures.WithTargetDir(t.TempDir))
+	st := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
+	return testServe(t, st, fun, io.NopCloser(bytes.NewBuffer(nil)), &T{
+		GitProtocol:   proto,
+		AdvertiseRefs: true,
+		StatelessRPC:  stateless,
+	})
+}
diff --git a/plumbing/transport/upload_pack_test.go b/plumbing/transport/upload_pack_test.go
index 81ee7ad8..a11a9efb 100644
--- a/plumbing/transport/upload_pack_test.go
+++ b/plumbing/transport/upload_pack_test.go
@@ -1,8 +1,17 @@
 package transport
 
 import (
+	"bytes"
+	"io"
 	"testing"
 
+	fixtures "github.com/go-git/go-git-fixtures/v5"
+	"github.com/go-git/go-git/v6/plumbing"
+	"github.com/go-git/go-git/v6/plumbing/cache"
+	"github.com/go-git/go-git/v6/plumbing/protocol/packp"
+	"github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
+	"github.com/go-git/go-git/v6/storage/filesystem"
+	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
 )
 
@@ -27,3 +36,32 @@ func (s *UploadPackSuite) TestUploadPackAdvertiseV1() {
 	buf := testAdvertise(s.T(), UploadPack, "version=1", false)
 	s.Containsf(buf.String(), "version 1", "advertisement should contain version 1")
 }
+
+func (s *UploadPackSuite) TestUploadPackAlwaysUseSidebandWhenAvailable() {
+	dot := fixtures.Basic().One().DotGit(fixtures.WithTargetDir(s.T().TempDir))
+	st := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
+	upreq := packp.NewUploadRequest()
+	upreq.Capabilities.Add(capability.Sideband64k)
+	upreq.Capabilities.Add(capability.NoProgress)
+	iter, err := st.IterEncodedObjects(plumbing.AnyObject)
+	require.NoError(s.T(), err)
+	defer iter.Close()
+	obj, err := iter.Next()
+	require.NoError(s.T(), err)
+	upreq.Wants = append(upreq.Wants, obj.Hash())
+
+	var uphav packp.UploadHaves
+	uphav.Done = true
+
+	var reqW bytes.Buffer
+	require.NoError(s.T(), upreq.Encode(&reqW))
+	require.NoError(s.T(), uphav.Encode(&reqW))
+	buf := testServe(s.T(), st, UploadPack, io.NopCloser(&reqW), &UploadPackOptions{
+		GitProtocol:   "version=1",
+		AdvertiseRefs: false,
+		StatelessRPC:  true,
+	})
+
+	expected := "0008NAK\n0009\x01PACK" // NAK response + sideband pack header + PACK marker
+	s.Equal(expected, buf.String()[:len(expected)], "pack file should be sent via sideband")
+}