Skip to content

Commit 14c3fa8

Browse files
committed
Introduce a hook to capture the data from info/refs from the caller
Entire-Checkpoint: 231c4ecc897a
1 parent 66ab8c4 commit 14c3fa8

12 files changed

Lines changed: 259 additions & 166 deletions

File tree

cmd/git-sync/main.go

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ func runSyncLike(ctx context.Context, name string, args []string, dryRun bool, d
5959

6060
fs.StringVar(&req.Source.URL, "source-url", "", "source repository URL")
6161
fs.StringVar(&req.Target.URL, "target-url", "", "target repository URL")
62-
fs.BoolVar(&req.Source.FollowInfoRefsRedirect, "source-follow-info-refs-redirect", envBool("GITSYNC_SOURCE_FOLLOW_INFO_REFS_REDIRECT"), "send follow-up source RPCs to the final /info/refs redirect host")
63-
fs.BoolVar(&req.Target.FollowInfoRefsRedirect, "target-follow-info-refs-redirect", envBool("GITSYNC_TARGET_FOLLOW_INFO_REFS_REDIRECT"), "send follow-up target RPCs to the final /info/refs redirect host")
6462

6563
fs.StringVar(&sourceAuth.Token, "source-token", envOr("GITSYNC_SOURCE_TOKEN", ""), "source token/password")
6664
fs.StringVar(&targetAuth.Token, "target-token", envOr("GITSYNC_TARGET_TOKEN", ""), "target token/password")
@@ -165,8 +163,6 @@ func runBootstrap(ctx context.Context, args []string) error {
165163

166164
fs.StringVar(&req.Source.URL, "source-url", "", "source repository URL")
167165
fs.StringVar(&req.Target.URL, "target-url", "", "target repository URL")
168-
fs.BoolVar(&req.Source.FollowInfoRefsRedirect, "source-follow-info-refs-redirect", envBool("GITSYNC_SOURCE_FOLLOW_INFO_REFS_REDIRECT"), "send follow-up source RPCs to the final /info/refs redirect host")
169-
fs.BoolVar(&req.Target.FollowInfoRefsRedirect, "target-follow-info-refs-redirect", envBool("GITSYNC_TARGET_FOLLOW_INFO_REFS_REDIRECT"), "send follow-up target RPCs to the final /info/refs redirect host")
170166

171167
fs.StringVar(&sourceAuth.Token, "source-token", envOr("GITSYNC_SOURCE_TOKEN", ""), "source token/password")
172168
fs.StringVar(&targetAuth.Token, "target-token", envOr("GITSYNC_TARGET_TOKEN", ""), "target token/password")
@@ -241,12 +237,9 @@ func runProbe(ctx context.Context, args []string) error {
241237
var jsonOutput bool
242238
var sourceAuth gitsync.EndpointAuth
243239
var targetAuth gitsync.EndpointAuth
244-
var targetFollowInfoRefsRedirect bool
245240
req := unstable.ProbeRequest{}
246241
fs.StringVar(&req.Source.URL, "source-url", "", "source repository URL")
247242
targetURL := fs.String("target-url", "", "optional target repository URL")
248-
fs.BoolVar(&req.Source.FollowInfoRefsRedirect, "source-follow-info-refs-redirect", envBool("GITSYNC_SOURCE_FOLLOW_INFO_REFS_REDIRECT"), "send follow-up source RPCs to the final /info/refs redirect host")
249-
fs.BoolVar(&targetFollowInfoRefsRedirect, "target-follow-info-refs-redirect", envBool("GITSYNC_TARGET_FOLLOW_INFO_REFS_REDIRECT"), "send follow-up target RPCs to the final /info/refs redirect host")
250243
fs.StringVar(&sourceAuth.Token, "source-token", envOr("GITSYNC_SOURCE_TOKEN", ""), "source token/password")
251244
fs.StringVar(&targetAuth.Token, "target-token", envOr("GITSYNC_TARGET_TOKEN", ""), "target token/password")
252245
fs.StringVar(&sourceAuth.Username, "source-username", envOr("GITSYNC_SOURCE_USERNAME", "git"), "source basic auth username")
@@ -281,10 +274,7 @@ func runProbe(ctx context.Context, args []string) error {
281274
return usageError("probe requires a source repository URL")
282275
}
283276
if *targetURL != "" {
284-
req.Target = &gitsync.Endpoint{
285-
URL: *targetURL,
286-
FollowInfoRefsRedirect: targetFollowInfoRefsRedirect,
287-
}
277+
req.Target = &gitsync.Endpoint{URL: *targetURL}
288278
}
289279

290280
result, err := unstable.New(unstable.Options{
@@ -308,7 +298,6 @@ func runFetch(ctx context.Context, args []string) error {
308298
req := unstable.FetchRequest{}
309299

310300
fs.StringVar(&req.Source.URL, "source-url", "", "source repository URL")
311-
fs.BoolVar(&req.Source.FollowInfoRefsRedirect, "source-follow-info-refs-redirect", envBool("GITSYNC_SOURCE_FOLLOW_INFO_REFS_REDIRECT"), "send follow-up source RPCs to the final /info/refs redirect host")
312301
fs.StringVar(&sourceAuth.Token, "source-token", envOr("GITSYNC_SOURCE_TOKEN", ""), "source token/password")
313302
fs.StringVar(&sourceAuth.Username, "source-username", envOr("GITSYNC_SOURCE_USERNAME", "git"), "source basic auth username")
314303
fs.StringVar(&sourceAuth.BearerToken, "source-bearer-token", envOr("GITSYNC_SOURCE_BEARER_TOKEN", ""), "source bearer token")
@@ -453,8 +442,6 @@ sync flags:
453442
--target-bearer-token ...
454443
--source-insecure-skip-tls-verify
455444
--target-insecure-skip-tls-verify
456-
--source-follow-info-refs-redirect
457-
--target-follow-info-refs-redirect
458445
-v
459446
460447
replicate flags:
@@ -476,8 +463,6 @@ replicate flags:
476463
--target-bearer-token ...
477464
--source-insecure-skip-tls-verify
478465
--target-insecure-skip-tls-verify
479-
--source-follow-info-refs-redirect
480-
--target-follow-info-refs-redirect
481466
-v
482467
483468
plan flags:
@@ -501,8 +486,6 @@ plan flags:
501486
--target-bearer-token ...
502487
--source-insecure-skip-tls-verify
503488
--target-insecure-skip-tls-verify
504-
--source-follow-info-refs-redirect
505-
--target-follow-info-refs-redirect
506489
-v
507490
508491
bootstrap flags:
@@ -523,8 +506,6 @@ bootstrap flags:
523506
--target-bearer-token ...
524507
--source-insecure-skip-tls-verify
525508
--target-insecure-skip-tls-verify
526-
--source-follow-info-refs-redirect
527-
--target-follow-info-refs-redirect
528509
-v
529510
530511
probe flags:
@@ -541,8 +522,6 @@ probe flags:
541522
--target-bearer-token ...
542523
--source-insecure-skip-tls-verify
543524
--target-insecure-skip-tls-verify
544-
--source-follow-info-refs-redirect
545-
--target-follow-info-refs-redirect
546525
547526
fetch flags:
548527
--branch main,dev
@@ -557,7 +536,6 @@ fetch flags:
557536
--source-username git
558537
--source-bearer-token ...
559538
--source-insecure-skip-tls-verify
560-
--source-follow-info-refs-redirect
561539
`, unstable.DefaultMaterializedMaxObjects)
562540
if message == "" {
563541
return errors.New(strings.TrimSpace(usage))

cmd/git-sync/main_test.go

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -270,47 +270,6 @@ func TestRun_Replicate_SubcommandRejectsForce(t *testing.T) {
270270
}
271271
}
272272

273-
func TestRun_Fetch_SourceFollowInfoRefsRedirectFlag(t *testing.T) {
274-
sourceRepo, sourceFS := newSourceRepo(t)
275-
makeCommits(t, sourceRepo, sourceFS, 1)
276-
277-
sourceServer := newSmartHTTPRepoServer(t, sourceRepo)
278-
defer sourceServer.Close()
279-
280-
entry := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
281-
switch {
282-
case r.Method == http.MethodGet && r.URL.Path == sourceServer.repoPath+"/info/refs":
283-
http.Redirect(w, r, sourceServer.server.URL+r.URL.Path+"?"+r.URL.RawQuery, http.StatusTemporaryRedirect)
284-
case r.Method == http.MethodPost:
285-
http.Error(w, "entry domain rejects packs", http.StatusMethodNotAllowed)
286-
default:
287-
http.NotFound(w, r)
288-
}
289-
}))
290-
defer entry.Close()
291-
292-
output, err := captureStdout(func() error {
293-
return run(context.Background(), []string{
294-
"fetch",
295-
"--source-follow-info-refs-redirect",
296-
"--branch", testBranch,
297-
"--json",
298-
entry.URL + sourceServer.repoPath,
299-
})
300-
})
301-
if err != nil {
302-
t.Fatalf("run fetch: %v", err)
303-
}
304-
305-
var result map[string]any
306-
if err := json.Unmarshal([]byte(output), &result); err != nil {
307-
t.Fatalf("decode fetch json: %v\noutput=%s", err, output)
308-
}
309-
if got, ok := result["fetchedObjects"].(float64); !ok || got == 0 {
310-
t.Fatalf("expected fetched objects from redirected source, got %#v", result["fetchedObjects"])
311-
}
312-
}
313-
314273
func captureStdout(fn func() error) (string, error) {
315274
old := os.Stdout
316275
r, w, err := os.Pipe()

internal/gitproto/smarthttp.go

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"net/http"
10+
"net/url"
1011

1112
"github.com/go-git/go-git/v6/plumbing/protocol/packp/capability"
1213
"github.com/go-git/go-git/v6/plumbing/transport"
@@ -46,15 +47,21 @@ type Conn struct {
4647
HTTP *http.Client
4748
Auth transport.AuthMethod
4849

49-
// FollowInfoRefsRedirect, when true, rewrites Endpoint.Scheme and
50-
// Endpoint.Host to the final URL returned by RequestInfoRefs after
51-
// HTTP redirects. Subsequent PostRPC* calls then target the
52-
// redirected host directly, matching vanilla git's smart-HTTP
53-
// behaviour for discovery-aware servers that 307 /info/refs to a
54-
// hosting replica. Endpoint.Path is never modified — it still
55-
// contains the repo path. Off by default to preserve behaviour for
56-
// callers that rely on Endpoint being stable.
57-
FollowInfoRefsRedirect bool
50+
// AfterInfoRefs, when set, is called with the /info/refs response
51+
// after RequestInfoRefs has read and bounded the body. The presented
52+
// res.Body is a fresh reader over the buffered advertisement, so the
53+
// hook can read it (or not) without affecting RequestInfoRefs's
54+
// return value. A non-nil return rewrites Endpoint.Scheme and
55+
// Endpoint.Host to those of the returned URL, so subsequent PostRPC*
56+
// calls target the chosen host. Endpoint.Path is never modified.
57+
// Returning nil leaves the endpoint unchanged.
58+
//
59+
// Use this for discovery-aware redirection: callers can read response
60+
// headers, follow the post-redirect URL, or implement protocol-specific
61+
// replica advertisement and pick a host. See
62+
// pkg/gitsync.FollowRedirectHook for the vanilla-git case (pin to
63+
// whatever http.Client redirected to).
64+
AfterInfoRefs func(*http.Response) *url.URL
5865
}
5966

6067
// NewConn creates a new connection to the given endpoint.
@@ -119,13 +126,6 @@ func RequestInfoRefs(ctx context.Context, conn *Conn, service transport.Service,
119126
if err := httpError(res); err != nil {
120127
return nil, err
121128
}
122-
if conn.FollowInfoRefsRedirect && res.Request != nil && res.Request.URL != nil {
123-
final := res.Request.URL
124-
if final.Host != conn.Endpoint.Host || final.Scheme != conn.Endpoint.Scheme {
125-
conn.Endpoint.Scheme = final.Scheme
126-
conn.Endpoint.Host = final.Host
127-
}
128-
}
129129
// Bound the read to prevent unbounded memory allocation (issue #9).
130130
const maxInfoRefsSize = 64 * 1024 * 1024 // 64 MiB
131131
lr := io.LimitReader(res.Body, maxInfoRefsSize+1)
@@ -136,6 +136,17 @@ func RequestInfoRefs(ctx context.Context, conn *Conn, service transport.Service,
136136
if int64(len(data)) > maxInfoRefsSize {
137137
return nil, fmt.Errorf("info/refs response exceeds %d byte limit", maxInfoRefsSize)
138138
}
139+
if conn.AfterInfoRefs != nil {
140+
res.Body = io.NopCloser(bytes.NewReader(data))
141+
if pin := conn.AfterInfoRefs(res); pin != nil {
142+
if pin.Scheme != "" {
143+
conn.Endpoint.Scheme = pin.Scheme
144+
}
145+
if pin.Host != "" {
146+
conn.Endpoint.Host = pin.Host
147+
}
148+
}
149+
}
139150
return data, nil
140151
}
141152

0 commit comments

Comments
 (0)