detached 27d9aa4

fix(githubapi): accept the api/v3 base that GoReleaser appends to the API URL

commit
27d9aa499afdca0f0f8aaeec524d2585ad5c95e4
author
brice · · 2026-10-21 16:12 UTC
committer
brice · · 2026-10-21 17:53 UTC
parent
d50b7cd
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +25 -0
M internal/adapters/githubapi/handler.go +5 -0
@@ -185,6 +185,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
185185
186186 // route recognises the endpoints. ref is "" for /tarball.
187187 func route(rest string) (kind, owner, repo, ref string, ok bool) {
188+ // go-github's enterprise client (used by GoReleaser) appends api/v3
189+ // to a custom API base. Keep the canonical URLs, accepting that alias too.
190+ if strings.HasPrefix(rest, "/api/v3/") {
191+ rest = strings.TrimPrefix(rest, "/api/v3")
192+ }
188193 if kind, owner, repo, ref, ok = releaseRoute(rest); ok {
189194 return
190195 }
@@ -185,6 +185,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
185 185
186 // route recognises the endpoints. ref is "" for /tarball.186 // route recognises the endpoints. ref is "" for /tarball.
187 func route(rest string) (kind, owner, repo, ref string, ok bool) {187 func route(rest string) (kind, owner, repo, ref string, ok bool) {
188+ // go-github's enterprise client (used by GoReleaser) appends api/v3
189+ // to a custom API base. Keep the canonical URLs, accepting that alias too.
190+ if strings.HasPrefix(rest, "/api/v3/") {
191+ rest = strings.TrimPrefix(rest, "/api/v3")
192+ }
188 if kind, owner, repo, ref, ok = releaseRoute(rest); ok {193 if kind, owner, repo, ref, ok = releaseRoute(rest); ok {
189 return194 return
190 }195 }
M internal/adapters/githubapi/releases_test.go +20 -0
@@ -75,6 +75,26 @@ func TestReleaseRoutes(t *testing.T) {
7575 require.False(t, ok, path)
7676 }
7777 }
78+
79+// GoReleaser's go-github enterprise client appends api/v3 to the documented
80+// custom API URL. The alias must dispatch writes through the same auth/service.
81+func TestReleaseEnterpriseBase(t *testing.T) {
82+ api := mocks.NewGitHubReleaseAPI(t)
83+ h := New(Config{Releases: api})
84+ body := []byte(`{"tag_name":"v1"}`)
85+ api.EXPECT().CreateRelease(mock.Anything, domain.Anonymous(), "acme", "proj", body).Return([]byte(`{"id":4}`), nil).Once()
86+ w := releaseRequest(t, h, "POST", "/api/v3"+releasePath, string(body), nil)
87+ require.Equal(t, 201, w.Code)
88+ require.JSONEq(t, `{"id":4}`, w.Body.String())
89+ for _, path := range []string{releasePath, releasePath + "/4", releasePath + "/4/assets", releaseAssetPath, releasePath + "/latest", releasePath + "/tags/v1/patch"} {
90+ kind, owner, repo, ref, ok := route(path)
91+ aliasKind, aliasOwner, aliasRepo, aliasRef, aliasOK := route("/api/v3" + path)
92+ require.Equal(t, []any{kind, owner, repo, ref, ok}, []any{aliasKind, aliasOwner, aliasRepo, aliasRef, aliasOK})
93+ }
94+ _, _, _, _, ok := route("/api/v3repos/acme/proj/releases")
95+ require.False(t, ok)
96+}
97+
7898 func TestReleaseDispatch(t *testing.T) {
7999 for _, method := range []string{"GET", "HEAD"} {
80100 t.Run(method, func(t *testing.T) {
@@ -75,6 +75,26 @@ func TestReleaseRoutes(t *testing.T) {
75 require.False(t, ok, path)75 require.False(t, ok, path)
76 }76 }
77 }77 }
78+
79+// GoReleaser's go-github enterprise client appends api/v3 to the documented
80+// custom API URL. The alias must dispatch writes through the same auth/service.
81+func TestReleaseEnterpriseBase(t *testing.T) {
82+ api := mocks.NewGitHubReleaseAPI(t)
83+ h := New(Config{Releases: api})
84+ body := []byte(`{"tag_name":"v1"}`)
85+ api.EXPECT().CreateRelease(mock.Anything, domain.Anonymous(), "acme", "proj", body).Return([]byte(`{"id":4}`), nil).Once()
86+ w := releaseRequest(t, h, "POST", "/api/v3"+releasePath, string(body), nil)
87+ require.Equal(t, 201, w.Code)
88+ require.JSONEq(t, `{"id":4}`, w.Body.String())
89+ for _, path := range []string{releasePath, releasePath + "/4", releasePath + "/4/assets", releaseAssetPath, releasePath + "/latest", releasePath + "/tags/v1/patch"} {
90+ kind, owner, repo, ref, ok := route(path)
91+ aliasKind, aliasOwner, aliasRepo, aliasRef, aliasOK := route("/api/v3" + path)
92+ require.Equal(t, []any{kind, owner, repo, ref, ok}, []any{aliasKind, aliasOwner, aliasRepo, aliasRef, aliasOK})
93+ }
94+ _, _, _, _, ok := route("/api/v3repos/acme/proj/releases")
95+ require.False(t, ok)
96+}
97+
78 func TestReleaseDispatch(t *testing.T) {98 func TestReleaseDispatch(t *testing.T) {
79 for _, method := range []string{"GET", "HEAD"} {99 for _, method := range []string{"GET", "HEAD"} {
80 t.Run(method, func(t *testing.T) {100 t.Run(method, func(t *testing.T) {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 42

↑↓ moveenter openesc close