ssh: fall back to v1 after v2 probe rejection · Entire
ssh: fall back to v1 after v2 probe rejection
ade4959→main· Soph·2mo ago·2 files·+100 added/-0 removed
Changes
2
internal/gitproto
Mrefs.go+27
Mrefs_test.go+73
45 unmodified lines
46
47
48
49
50
51
52
53
54
25 unmodified lines
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
45 unmodified lines
case "auto", "v2":
data, err := RequestInfoRefs(ctx, conn, transport.UploadPackService, "version=2")
if err != nil {
if protocolMode == "auto" && shouldFallbackToV1AfterV2ProbeError(conn, err) {
return listSourceRefsAutoV1(ctx, conn)
}
return nil, nil, err
}
if caps, err := DecodeV2Capabilities(bytes.NewReader(data)); err == nil {
25 unmodified lines
}
}
func listSourceRefsAutoV1(ctx context.Context, conn Conn) ([]*plumbing.Reference, *RefService, error) {
adv, refs, err := listSourceRefsV1(ctx, conn)
if err != nil {
return nil, nil, err
}
return refs, &RefService{Protocol: "v1", V1Adv: adv, HeadTarget: headTargetFromAdv(adv)}, nil
}
func shouldFallbackToV1AfterV2ProbeError(conn Conn, err error) bool {
if conn == nil || conn.Endpoint() == nil {
return false
}
switch conn.Endpoint().Scheme {
case "ssh", "git+ssh":
default:
return false
}
msg := err.Error()
return strings.Contains(msg, "Invalid command:") &&
strings.Contains(msg, "GIT_PROTOCOL='version=2'") &&
strings.Contains(msg, "git-upload-pack")
}
// AdvertisedRefsV1 fetches and decodes v1 advertised refs for the given service.
func AdvertisedRefsV1(ctx context.Context, conn Conn, service string) (*packp.AdvRefs, error) {
data, err := RequestInfoRefs(ctx, conn, service, "")
Minternal/gitproto/refs.go+27
2 unmodified lines
3
4
5
6
7
8
9
10
191 unmodified lines
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
2 unmodified lines
import (
"context"
"errors"
"io"
"net/url"
"strings"
"testing"
191 unmodified lines
t.Fatal("expected error for unsupported protocol mode")
}
}
func TestListSourceRefsAutoFallsBackToV1AfterSSHV2ProbeCommandRejection(t *testing.T) {
t.Parallel()
conn := &stubConn{
reqInfoRefs: func(_ context.Context, _ string, gitProtocol string) ([]byte, error) {
if gitProtocol == "version=2" {
return nil, errors.New("request info refs: git-upload-pack info-refs: wait for ssh command: exit status 1: Invalid command: GIT_PROTOCOL='version=2' git-upload-pack 'entireio/cli.git'"
}
var body strings.Builder
if _, err := pktline.Writef(&body, "# service=%s\n", transport.UploadPackService); err != nil {
t.Fatalf("write smart service line: %v", err)
}
if err := pktline.WriteFlush(&body); err != nil {
t.Fatalf("write smart flush: %v", err)
}
if _, err := pktline.Writef(&body, "%s HEAD\x00%s\n", strings.Repeat("a", 40), capability.SymRef+"=HEAD:refs/heads/main"); err != nil {
t.Fatalf("write advertised head: %v", err)
}
if _, err := pktline.Writef(&body, "%s refs/heads/main\n", strings.Repeat("a", 40)); err != nil {
t.Fatalf("write advertised ref: %v", err)
}
if err := pktline.WriteFlush(&body); err != nil {
t.Fatalf("write trailing flush: %v", err)
}
return []byte(body.String()), nil
},
}
refs, svc, err := ListSourceRefs(t.Context(), conn, "auto", nil)
if err != nil {
t.Fatalf("ListSourceRefs(auto) error = %v", err)
}
if svc.Protocol != "v1" {
t.Fatalf("protocol = %q, want v1", svc.Protocol)
}
if got := svc.HeadTarget.String(); got != "refs/heads/main" {
t.Fatalf("head target = %q, want refs/heads/main", got)
}
foundMain := false
for _, ref := range refs {
if ref.Name().String() == "refs/heads/main" {
foundMain = true
break
}
}
if !foundMain {
t.Fatalf("refs = %#v, want refs/heads/main to be advertised", refs)
}
}
type stubConn struct {
reqInfoRefs func(ctx context.Context, service string, gitProtocol string) ([]byte, error)
}
func (s *stubConn) RequestInfoRefs(ctx context.Context, service string, gitProtocol string) ([]byte, error) {
return s.reqInfoRefs(ctx, service, gitProtocol)
}
func (s *stubConn) PostRPCStreamBody(context.Context, string, io.Reader, bool, string) (io.ReadCloser, error) {
return nil, errors.New("unexpected PostRPCStreamBody call")
}
func (s *stubConn) Endpoint() *url.URL { return &url.URL{Scheme: "ssh", Host: "github.com"} }
func (s *stubConn) ProgressWriter() io.Writer { return nil }
func (s *stubConn) SetProgressWriter(io.Writer) {}
func (s *stubConn) Close() error { return nil }