useragent: identify git-sync in outbound User-Agent · Entire
useragent: identify git-sync in outbound User-Agent
40abe29→main·
pjbgf·1mo ago·7 files·+86 added/-9 removed
Replace the bare go-git User-Agent on outbound requests with a
git-sync-flavoured string. Git wire-protocol traffic (smart-HTTP
info-refs and upload-pack/receive-pack RPCs, plus the protocol v2
"agent=" capability) sends "git-sync/
A new internal/useragent package centralises the format and holds a package-level Version var. The CLI wires this from versioninfo.Version at startup so --version and the User-Agent agree; SDK consumers can override it before issuing requests.
Assisted-by: Claude Opus 4.7 noreply@anthropic.com Signed-off-by: Paulo Gomes paulo@entire.io
Sessions
fb4821afd3f1View transcript
Changes
7
cmd/git-sync
Mmain.go+3
internal
gitproto
Mcapability.go+2/-1
Mcapability_test.go+3/-2
Msmarthttp.go+4/-4
strategy/bootstrap
Mbootstrap.go+2/-2
useragent
Auseragent.go+27
Auseragent_test.go+45
6 unmodified lines
7
8
9
10
11
12
13
14
15
16
17
18
19
6 unmodified lines
"os"
"strings"
"entire.io/entire/git-sync/cmd/git-sync/internal/versioninfo"
"entire.io/entire/git-sync/internal/useragent"
"github.com/spf13/cobra"
)
func main() {
useragent.Version = versioninfo.Version
err := run(context.Background(), os.Args[1:])
if err == nil {
return
}
Mcmd/git-sync/main.go+3
5 unmodified lines
6
7
8
9
10
11
12
54 unmodified lines
67
68
69
69
70
71
72
73
5 unmodified lines
"sort"
"strings"
"entire.io/entire/git-sync/internal/useragent"
"github.com/go-git/go-git/v6/plumbing/protocol/capability";
54 unmodified lines
func (c *V2Capabilities) RequestCapabilities() []string {
var caps []string
if agent := c.Value("agent"); agent != "" {
caps = append(caps, "agent="+capability.DefaultAgent())
caps = append(caps, "agent="+useragent.GoGit())
}
return caps
}
Minternal/gitproto/capability.go+2/-1
2 unmodified lines
3
4
5
6
7
8
9
159 unmodified lines
169
170
171
171
172
172
173
174
175
176
2 unmodified lines
import (
"testing"
"entire.io/entire/git-sync/internal/useragent"
"github.com/go-git/go-git/v6/plumbing/protocol/capability"
)
159 unmodified lines
if len(got) != 1 {
t.Fatalf("RequestCapabilities() returned %d items, want 1", len(got))
}
if got[0] != "agent="+capability.DefaultAgent() {
t.Errorf("RequestCapabilities()[0] = %q, want %q", got[0], "agent="+capability.DefaultAgent())
}
if got[0] != "agent="+useragent.GoGit() {
t.Errorf("RequestCapabilities()[0] = %q, want %q", got[0], "agent="+useragent.GoGit())
}
// Without agent, RequestCapabilities should return empty.
Minternal/gitproto/capability_test.go+3/-2
13 unmodified lines
14
15
16
17
17
18
19
20
507 unmodified lines
528
529
530
531
531
532
533
534
128 unmodified lines
663
664
665
666
666
667
668
669
153 unmodified lines
823
824
825
826
826
827
828
829
13 unmodified lines
"os"
"strings"
"github.com/go-git/go-git/v6/plumbing/protocol/capability"
"entire.io/entire/git-sync/internal/useragent"
transporthttp "github.com/go-git/go-git/v6/plumbing/transport/http"
)
507 unmodified lines
}
req.Header.Set("Content-Type", fmt.Sprintf("application/x-%s-request", service))
req.Header.Set("Accept", fmt.Sprintf("application/x-%s-result", service))
req.Header.Set("User-Agent", capability.DefaultAgent())
req.Header.Set("User-Agent", useragent.GoGit())
req.Header.Set(StatsPhaseHeader, phase)
if v2 {
rq.Header.Set("Git-Protocol", GitProtocolV2)
}
Minternal/gitproto/smarthttp.go+4/-4
17 unmodified lines
18
19
20
21
21
22
23
24
25
26
27
28
1309 unmodified lines
1338
1339
1340
1341
1341
1342
1343
1344
17 unmodified lines
git "github.com/go-git/go-git/v6"
"github.com/go-git/go-git/v6/plumbing"
"github.com/go-git/go-git/v6/plumbing/protocol/capability"
"entire.io/entire/git-sync/internal/convert"
"entire.io/entire/git-sync/internal/gitproto"
"entire.io/entire/git-sync/internal/planner"
"entire.io/entire/git-sync/internal/useragent"
const (
1309 unmodified lines
}
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("X-Github-Api-Version", "2022-11-28")
req.Header.Set("User-Agent", capability.DefaultAgent())
req.Header.Set("User-Agent", useragent.Plain())
req.Header.Set(gitproto.StatsPhaseHeader, "github repo metadata")
resp, err := httpConn.HTTP.Do(req)
if err != nil {
Minternal/strategy/bootstrap/bootstrap.go+2/-2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Package useragent builds the User-Agent strings git-sync advertises to
// remote services. Two flavours: GoGit for git wire-protocol traffic
// (HTTP smart-protocol requests and the protocol-level "agent="
// capability), and Plain for non-git HTTP requests such as provider
// metadata APIs.
package useragent
import "github.com/go-git/go-git/v6/plumbing/protocol/capability"
// Version is the git-sync version advertised in User-Agent strings.
// CLI builds set this from versioninfo.Version; SDK consumers may
// overwrite it before issuing any requests if they want to identify a
// different version.
var Version = "dev"
// GoGit returns the User-Agent for git wire-protocol traffic. Format:
// "git-sync/<version> go-git/<go-git-version>". The go-git suffix is
// preserved because servers and operators commonly key off it.
func GoGit() string {
return "git-sync/" + Version + " " + capability.DefaultAgent()
}
// Plain returns the User-Agent for non-git HTTP requests (e.g. provider
// REST APIs). Format: "git-sync/<version>".
func Plain() string {
return "git-sync/" + Version
}
Ainternal/useragent/useragent.go+27
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package useragent
import (
"strings"
"testing"
"github.com/go-git/go-git/v6/plumbing/protocol/capability"
)
func TestGoGit(t *testing.T) {
t.Parallel()
got := GoGit()
wantPrefix := "git-sync/" + Version + " "
if !strings.HasPrefix(got, wantPrefix) {
t.Errorf("GoGit() = %q, want prefix %q", got, wantPrefix)
}
if !strings.Contains(got, capability.DefaultAgent()) {
t.Errorf("GoGit() = %q, want it to contain %q", got, capability.DefaultAgent())
}
}
func TestPlain(t *testing.T) {
t.Parallel()
got := Plain()
want := "git-sync/" + Version
if got != want {
t.Errorf("Plain() = %q, want %q", got, want)
}
}
func TestVersionOverride(t *testing.T) {
// Not parallel — mutates package-level Version.
orig := Version
t.Cleanup(func() { Version = orig })
Version = "1.2.3"
if got, want := Plain(), "git-sync/1.2.3"; got != want {
t.Errorf("Plain() with overridden Version = %q, want %q", got, want)
}
if got, want := GoGit(), "git-sync/1.2.3 "+capability.DefaultAgent(); got != want {
t.Errorf("GoGit() with overridden Version = %q, want %q", got, want)
}
}
}