From a665e0c50d3919be6749024071061a488c3696ac Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Mon, 17 Aug 2026 12:44:08 +0100 Subject: [PATCH] Delegate GitHub commit resolution to forge --- go.mod | 11 +++++---- go.sum | 23 +++++++++++------- source/forge/forge_test.go | 12 +--------- source/forge/github.go | 45 ++++++++++++++++++----------------- source/forge/verifier_test.go | 2 +- sync_test.go | 7 ++++-- 6 files changed, 52 insertions(+), 48 deletions(-) diff --git a/go.mod b/go.mod index e3c078a..0e68545 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,9 @@ require ( github.com/git-pkgs/archives v0.5.1 github.com/git-pkgs/attestation v0.1.0 github.com/git-pkgs/cooldown v0.1.1 + github.com/git-pkgs/forge v0.9.0 github.com/git-pkgs/integrity v0.1.1 - github.com/git-pkgs/purl v0.1.15 + github.com/git-pkgs/purl v0.1.16 github.com/git-pkgs/registries v0.7.0 github.com/git-pkgs/sbom v0.1.5 github.com/git-pkgs/sigstore v0.1.2 @@ -146,6 +147,8 @@ require ( github.com/google/certificate-transparency-go v1.3.3 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-containerregistry v0.21.7 // indirect + github.com/google/go-github/v82 v82.0.0 // indirect + github.com/google/go-querystring v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gordonklaus/ineffassign v0.2.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect @@ -276,14 +279,14 @@ require ( go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.54.0 // indirect + golang.org/x/crypto v0.55.0 // indirect golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect golang.org/x/mod v0.38.0 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/term v0.45.0 // indirect - golang.org/x/text v0.40.0 // indirect - golang.org/x/tools v0.47.0 // indirect + golang.org/x/text v0.41.0 // indirect + golang.org/x/tools v0.48.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect google.golang.org/grpc v1.82.1 // indirect diff --git a/go.sum b/go.sum index c3c3daa..284a77e 100644 --- a/go.sum +++ b/go.sum @@ -232,14 +232,16 @@ github.com/git-pkgs/attestation v0.1.0 h1:aj10Ls05zHWPL7114r0Se8RKC/w8tJgfnzh3dU github.com/git-pkgs/attestation v0.1.0/go.mod h1:NSctZpcwmxCiTQ6z3b4X6SCT2N7fQtuPXvizkRgPgBU= github.com/git-pkgs/cooldown v0.1.1 h1:9OqqzCB8gANz/y44SmqGD0Jp8Qtu81D1sCbKl6Ehg7w= github.com/git-pkgs/cooldown v0.1.1/go.mod h1:v7APuK/UouTiu8mWQZbdDmj7DfxxkGUeuhjaRB5gv9E= +github.com/git-pkgs/forge v0.9.0 h1:/UI2pezqzSzZBiVroiChRHQnekHzY5JrBXPISiZl1Wk= +github.com/git-pkgs/forge v0.9.0/go.mod h1:DhaJu5jUKodB/MbRzYcG6CYkfNg4fBpRsdcxDndZ3xI= github.com/git-pkgs/integrity v0.1.1 h1:nHQ7SktOiGM1dOb5BFnkdtttG/6FCgE6r5ru6QnsGts= github.com/git-pkgs/integrity v0.1.1/go.mod h1:hxu24lcd230377hCF28JQW7sGcCbuNLqo/0ULeb+F1Q= github.com/git-pkgs/magic v0.2.0 h1:c7HqVxnP8c88EaVMH0/KraDFVTcmiXckRiSvNZEnvMQ= github.com/git-pkgs/magic v0.2.0/go.mod h1:3ndidt+yvFaI1M0aEkkzkOlFnLPkeVQASIUojazcxCI= github.com/git-pkgs/pom v0.1.5 h1:TGT8Az2OMxGWsXnSagtUMGzZm7Oax8HrSCteA+mi0qY= github.com/git-pkgs/pom v0.1.5/go.mod h1:ufdMBe1lKzqOeP9IUb9NPZ458xKV8E8NvuyBMxOfwIk= -github.com/git-pkgs/purl v0.1.15 h1:iQ3clh0Cw41rkM0rf24B7ShnN9Z+UtLMAFlNDUs+Qd4= -github.com/git-pkgs/purl v0.1.15/go.mod h1:PqCLVBDeZrZgHysR803/AntMELgIr2LFZVNCcwLH2m0= +github.com/git-pkgs/purl v0.1.16 h1:VAX6tv0hhdTENbkrGMoPZbOAl1Y8U1/ZnzoCsYuNBYM= +github.com/git-pkgs/purl v0.1.16/go.mod h1:7u7ora8tQdrkS7Auclr5v8dCJdjN4ej6AbrvYZi2b7k= github.com/git-pkgs/registries v0.7.0 h1:+LbOOMHbvjmXGfsi88hcGH+SfTXYsXA3UY5KYI5mB7s= github.com/git-pkgs/registries v0.7.0/go.mod h1:VCD4q+ZW0fInopzseg9rAmBEL553R2JQe60UHXtv26w= github.com/git-pkgs/sbom v0.1.5 h1:qWyZlqaeroTcImKm5qt65ElAQTYCjnLlvflzAj4B2Po= @@ -385,10 +387,15 @@ github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvj github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.21.7 h1:/vPFuVXDjtFREsVArW+0h1CIl5urnOhzei4X2DMW9IU= github.com/google/go-containerregistry v0.21.7/go.mod h1:kjSbt7/zMsKLWfnHrIvKvhXHUw91jbe9DNjPPJ32gXE= +github.com/google/go-github/v82 v82.0.0 h1:OH09ESON2QwKCUVMYmMcVu1IFKFoaZHwqYaUtr/MVfk= +github.com/google/go-github/v82 v82.0.0/go.mod h1:hQ6Xo0VKfL8RZ7z1hSfB4fvISg0QqHOqe9BP0qo+WvM= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= @@ -789,8 +796,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= -golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= @@ -854,8 +861,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -867,8 +874,8 @@ golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0t golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= -golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= diff --git a/source/forge/forge_test.go b/source/forge/forge_test.go index cd7e20c..257f56c 100644 --- a/source/forge/forge_test.go +++ b/source/forge/forge_test.go @@ -2,7 +2,6 @@ package forge import ( "context" - "encoding/json" "net/http" "net/http/httptest" "strings" @@ -15,7 +14,7 @@ func fakeGitHub(t *testing.T, owner, repo, tag, sha string, files map[string]str t.Helper() apiMux := http.NewServeMux() apiMux.HandleFunc("/repos/"+owner+"/"+repo+"/commits/"+tag, func(w http.ResponseWriter, r *http.Request) { - _ = json.NewEncoder(w).Encode(map[string]string{"sha": sha}) + _, _ = w.Write([]byte(sha)) }) apiSrv := httptest.NewServer(apiMux) t.Cleanup(apiSrv.Close) @@ -149,12 +148,3 @@ func TestResolveUnsupportedForge(t *testing.T) { t.Fatal("expected error for unsupported forge type") } } - -func TestIsHex(t *testing.T) { - if !isHex("abc123DEF") { - t.Error("hex string not recognised") - } - if isHex("ghijk") { - t.Error("non-hex accepted") - } -} diff --git a/source/forge/github.go b/source/forge/github.go index 60b77eb..1ed53bd 100644 --- a/source/forge/github.go +++ b/source/forge/github.go @@ -6,9 +6,11 @@ import ( "encoding/hex" "encoding/json" "fmt" + "net/http" "strings" "github.com/git-pkgs/attestation" + githubforge "github.com/git-pkgs/forge/github" "github.com/git-pkgs/purl" "golang.org/x/sync/errgroup" ) @@ -18,14 +20,24 @@ import ( // connections. const forgeFileConcurrency = 4 -const fullSHALen = 40 - type fileFetch struct { rf ResolvedFile att *Attestation attRaw []byte } +type userAgentTransport struct { + base http.RoundTripper + userAgent string +} + +func (t userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) { + clone := req.Clone(req.Context()) + clone.Header = req.Header.Clone() + clone.Header.Set("User-Agent", t.userAgent) + return t.base.RoundTrip(clone) +} + func (s *Source) resolveGitHub(ctx context.Context, p *purl.PURL, files []string) (*Resolved, error) { owner, repo, ref := p.Namespace, p.Name, p.Version sha, err := s.githubResolveSHA(ctx, owner, repo, ref) @@ -133,27 +145,16 @@ func (s *Source) fetchGitHubAttestation(ctx context.Context, owner, repo string, } func (s *Source) githubResolveSHA(ctx context.Context, owner, repo, ref string) (string, error) { - if len(ref) == fullSHALen && isHex(ref) { - return ref, nil - } - url := fmt.Sprintf("%s/repos/%s/%s/commits/%s", strings.TrimRight(s.opts.GitHubAPI, "/"), owner, repo, ref) - var resp struct { - SHA string `json:"sha"` + httpClient := *s.http.HTTPClient + transport := httpClient.Transport + if transport == nil { + transport = http.DefaultTransport } - if err := s.http.GetJSON(ctx, url, &resp); err != nil { - return "", fmt.Errorf("resolve %s/%s ref %q to commit: %w", owner, repo, ref, err) - } - if resp.SHA == "" { - return "", fmt.Errorf("resolve %s/%s ref %q: empty SHA in response", owner, repo, ref) - } - return resp.SHA, nil -} + httpClient.Transport = userAgentTransport{base: transport, userAgent: s.http.UserAgent} -func isHex(s string) bool { - for _, c := range s { - if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') { - return false - } + resolver, err := githubforge.NewCommitResolverWithBase(s.opts.GitHubAPI, "", &httpClient) + if err != nil { + return "", fmt.Errorf("create GitHub commit resolver: %w", err) } - return true + return resolver.ResolveCommit(ctx, owner, repo, ref) } diff --git a/source/forge/verifier_test.go b/source/forge/verifier_test.go index 72f8439..97a5c92 100644 --- a/source/forge/verifier_test.go +++ b/source/forge/verifier_test.go @@ -60,7 +60,7 @@ func attestationServer(t *testing.T, expectDigest [32]byte, bundleBody []byte, c t.Helper() apiMux := http.NewServeMux() apiMux.HandleFunc("/repos/o/r/commits/v1", func(w http.ResponseWriter, _ *http.Request) { - _ = json.NewEncoder(w).Encode(map[string]string{"sha": sha}) + _, _ = w.Write([]byte(sha)) }) apiMux.HandleFunc("/repos/o/r/attestations/sha256:"+hex.EncodeToString(expectDigest[:]), func(w http.ResponseWriter, _ *http.Request) { diff --git a/sync_test.go b/sync_test.go index bc52f30..54cce08 100644 --- a/sync_test.go +++ b/sync_test.go @@ -396,8 +396,11 @@ assets: func fakeGitHub(t *testing.T, owner, repo, tag, sha string, files map[string]string) (api, cdn string) { t.Helper() apiMux := http.NewServeMux() - apiMux.HandleFunc("/repos/"+owner+"/"+repo+"/commits/"+tag, func(w http.ResponseWriter, _ *http.Request) { - _ = json.NewEncoder(w).Encode(map[string]string{"sha": sha}) + apiMux.HandleFunc("/repos/"+owner+"/"+repo+"/commits/"+tag, func(w http.ResponseWriter, r *http.Request) { + if got := r.Header.Get("User-Agent"); !strings.HasPrefix(got, "pin/") { + t.Errorf("User-Agent = %q, want pin version", got) + } + _, _ = w.Write([]byte(sha)) }) apiSrv := httptest.NewServer(apiMux) t.Cleanup(apiSrv.Close)