Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions gitea/releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"os"
"path/filepath"
"strconv"

"code.gitea.io/sdk/gitea"
)
Expand All @@ -24,6 +25,7 @@ func (f *giteaForge) Releases() forge.ReleaseService {

func convertGiteaRelease(r *gitea.Release) forge.Release {
result := forge.Release{
ID: r.ID,
TagName: r.TagName,
Title: r.Title,
Body: r.Note,
Expand Down Expand Up @@ -88,10 +90,31 @@ func (s *giteaReleaseService) List(ctx context.Context, owner, repo string, opts
return all, nil
}

func (s *giteaReleaseService) Get(ctx context.Context, owner, repo, tag string) (*forge.Release, error) {
// getReleaseByTagOrID resolves a release by tag, falling back to a numeric
// release ID when the tag lookup returns 404.
func (s *giteaReleaseService) getReleaseByTagOrID(owner, repo, tag, op string) (*gitea.Release, error) {
r, resp, err := s.client.GetReleaseByTag(owner, repo, tag)
if err == nil {
return r, nil
}
if resp == nil || resp.StatusCode != http.StatusNotFound {
return nil, wrapErr(op, resp, err)
}
id, parseErr := strconv.ParseInt(tag, 10, 64)
if parseErr != nil {
return nil, forge.ErrNotFound
}
r, resp, err = s.client.GetRelease(owner, repo, id)
if err != nil {
return nil, wrapErr(op, resp, err)
}
return r, nil
}

func (s *giteaReleaseService) Get(ctx context.Context, owner, repo, tag string) (*forge.Release, error) {
r, err := s.getReleaseByTagOrID(owner, repo, tag, "get release")
if err != nil {
return nil, wrapErr("get release", resp, err)
return nil, err
}
result := convertGiteaRelease(r)
return &result, nil
Expand Down Expand Up @@ -142,9 +165,9 @@ func (s *giteaReleaseService) Create(ctx context.Context, owner, repo string, op
}

func (s *giteaReleaseService) Update(ctx context.Context, owner, repo, tag string, opts forge.UpdateReleaseOpts) (*forge.Release, error) {
existing, resp, err := s.client.GetReleaseByTag(owner, repo, tag)
existing, err := s.getReleaseByTagOrID(owner, repo, tag, "update release")
if err != nil {
return nil, wrapErr("update release", resp, err)
return nil, err
}

gOpts := gitea.EditReleaseOption{}
Expand Down Expand Up @@ -189,10 +212,11 @@ func (s *giteaReleaseService) Update(ctx context.Context, owner, repo, tag strin
}

func (s *giteaReleaseService) Delete(ctx context.Context, owner, repo, tag string) error {
existing, resp, err := s.client.GetReleaseByTag(owner, repo, tag)
existing, err := s.getReleaseByTagOrID(owner, repo, tag, "delete release")
if err != nil {
return wrapErr("delete release", resp, err)
return err
}
var resp *gitea.Response

resp, err = s.client.DeleteRelease(owner, repo, existing.ID)
if err != nil {
Expand All @@ -202,10 +226,11 @@ func (s *giteaReleaseService) Delete(ctx context.Context, owner, repo, tag strin
}

func (s *giteaReleaseService) UploadAsset(ctx context.Context, owner, repo, tag string, file *os.File) (*forge.ReleaseAsset, error) {
existing, resp, err := s.client.GetReleaseByTag(owner, repo, tag)
existing, err := s.getReleaseByTagOrID(owner, repo, tag, "upload release asset")
if err != nil {
return nil, wrapErr("upload release asset", resp, err)
return nil, err
}
var resp *gitea.Response

name := filepath.Base(file.Name())
a, resp, err := s.client.CreateReleaseAttachment(owner, repo, existing.ID, file, name)
Expand Down
182 changes: 182 additions & 0 deletions gitea/releases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import (
forge "github.com/git-pkgs/forge"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"

"code.gitea.io/sdk/gitea"
)

func TestCreateReleaseAlreadyExists(t *testing.T) {
Expand Down Expand Up @@ -124,3 +128,181 @@ func TestCreateReleaseNotFound(t *testing.T) {
t.Fatalf("expected ErrNotFound, got %v", err)
}
}

func TestConvertGiteaReleasePopulatesID(t *testing.T) {
rel := convertGiteaRelease(&gitea.Release{
ID: 777,
TagName: "v7.7.7",
Title: "Release 7.7.7",
IsDraft: true,
})
if rel.ID != 777 {
t.Errorf("ID: want 777, got %d", rel.ID)
}
if rel.TagName != "v7.7.7" {
t.Errorf("TagName: want %q, got %q", "v7.7.7", rel.TagName)
}
}

func TestGiteaReleaseGetByIDFallback(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/version", giteaVersionHandler)
mux.HandleFunc("GET /api/v1/repos/o/r/releases/tags/9", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message":"release not found"}`))
})
mux.HandleFunc("GET /api/v1/repos/o/r/releases/9", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":9,"tag_name":"v0.9.0-draft","name":"Draft 9","draft":true}`))
})
srv := httptest.NewServer(mux)
defer srv.Close()

f := New(srv.URL, "", nil)
rel, err := f.Releases().Get(context.Background(), "o", "r", "9")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if rel.ID != 9 {
t.Errorf("ID: want 9, got %d", rel.ID)
}
if rel.TagName != "v0.9.0-draft" {
t.Errorf("TagName: want %q, got %q", "v0.9.0-draft", rel.TagName)
}
if !rel.Draft {
t.Errorf("Draft: want true, got false")
}
}

func TestGiteaReleaseUpdateByIDFallback(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/version", giteaVersionHandler)
mux.HandleFunc("GET /api/v1/repos/o/r/releases/tags/9", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message":"release not found"}`))
})
mux.HandleFunc("GET /api/v1/repos/o/r/releases/9", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":9,"tag_name":"v0.9.0-draft","name":"Draft 9","draft":true}`))
})
mux.HandleFunc("PATCH /api/v1/repos/o/r/releases/9", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":9,"tag_name":"v0.9.0-draft","name":"Draft 9 renamed","draft":true}`))
})
srv := httptest.NewServer(mux)
defer srv.Close()

f := New(srv.URL, "", nil)
title := "Draft 9 renamed"
rel, err := f.Releases().Update(context.Background(), "o", "r", "9", forge.UpdateReleaseOpts{
Title: &title,
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if rel.ID != 9 {
t.Errorf("ID: want 9, got %d", rel.ID)
}
if rel.Title != "Draft 9 renamed" {
t.Errorf("Title: want %q, got %q", "Draft 9 renamed", rel.Title)
}
}

func TestGiteaReleaseDeleteByIDFallback(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/version", giteaVersionHandler)
mux.HandleFunc("GET /api/v1/repos/o/r/releases/tags/9", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message":"release not found"}`))
})
mux.HandleFunc("GET /api/v1/repos/o/r/releases/9", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":9,"tag_name":"v0.9.0-draft","draft":true}`))
})
mux.HandleFunc("DELETE /api/v1/repos/o/r/releases/9", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
srv := httptest.NewServer(mux)
defer srv.Close()

f := New(srv.URL, "", nil)
if err := f.Releases().Delete(context.Background(), "o", "r", "9"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}

func TestGiteaReleaseUploadAssetByIDFallback(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/version", giteaVersionHandler)
mux.HandleFunc("GET /api/v1/repos/o/r/releases/tags/9", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message":"release not found"}`))
})
mux.HandleFunc("GET /api/v1/repos/o/r/releases/9", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"id":9,"tag_name":"v0.9.0-draft","draft":true}`))
})
mux.HandleFunc("POST /api/v1/repos/o/r/releases/9/assets", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte(`{"id":100,"name":"notes.txt","size":11,"browser_download_url":"https://example.com/o/r/releases/download/9/notes.txt"}`))
})
srv := httptest.NewServer(mux)
defer srv.Close()

tmp := filepath.Join(t.TempDir(), "notes.txt")
if err := os.WriteFile(tmp, []byte("hello world"), 0o600); err != nil {
t.Fatalf("writing temp file: %v", err)
}
file, err := os.Open(tmp)
if err != nil {
t.Fatalf("opening temp file: %v", err)
}
defer func() { _ = file.Close() }()

f := New(srv.URL, "", nil)
asset, err := f.Releases().UploadAsset(context.Background(), "o", "r", "9", file)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if asset.Name != "notes.txt" {
t.Errorf("Name: want %q, got %q", "notes.txt", asset.Name)
}
if asset.Size != 11 {
t.Errorf("Size: want 11, got %d", asset.Size)
}
}

func TestGiteaReleaseGetMissingTagNotFound(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/version", giteaVersionHandler)
mux.HandleFunc("GET /api/v1/repos/o/r/releases/tags/nope", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message":"release not found"}`))
})
mux.HandleFunc("GET /api/v1/repos/o/r/releases/tags/4242", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message":"release not found"}`))
})
mux.HandleFunc("GET /api/v1/repos/o/r/releases/4242", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message":"release not found"}`))
})
srv := httptest.NewServer(mux)
defer srv.Close()

f := New(srv.URL, "", nil)
if _, err := f.Releases().Get(context.Background(), "o", "r", "nope"); !errors.Is(err, forge.ErrNotFound) {
t.Errorf("non-numeric missing tag: expected forge.ErrNotFound, got %v", err)
}
if _, err := f.Releases().Get(context.Background(), "o", "r", "4242"); !errors.Is(err, forge.ErrNotFound) {
t.Errorf("numeric missing tag: expected forge.ErrNotFound, got %v", err)
}
}
7 changes: 7 additions & 0 deletions github/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ func assertEqualInt(t *testing.T, field string, want, got int) {
}
}

func assertEqualInt64(t *testing.T, field string, want, got int64) {
t.Helper()
if want != got {
t.Errorf("%s: want %d, got %d", field, want, got)
}
}

func assertSliceEqual(t *testing.T, field string, want, got []string) {
t.Helper()
if len(want) != len(got) {
Expand Down
43 changes: 29 additions & 14 deletions github/releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"os"
"path/filepath"
"strconv"

"github.com/google/go-github/v82/github"
)
Expand All @@ -21,6 +22,7 @@ func (f *gitHubForge) Releases() forge.ReleaseService {

func convertGitHubRelease(r *github.RepositoryRelease) forge.Release {
result := forge.Release{
ID: r.GetID(),
TagName: r.GetTagName(),
Title: r.GetName(),
Body: r.GetBody(),
Expand Down Expand Up @@ -100,14 +102,36 @@ func (s *gitHubReleaseService) List(ctx context.Context, owner, repo string, opt
return all, nil
}

func (s *gitHubReleaseService) Get(ctx context.Context, owner, repo, tag string) (*forge.Release, error) {
// Draft releases 404 by tag on GitHub, so fall back to treating tag as a release ID.
func (s *gitHubReleaseService) getReleaseByTagOrID(ctx context.Context, owner, repo, tag string) (*github.RepositoryRelease, error) {
r, resp, err := s.client.Repositories.GetReleaseByTag(ctx, owner, repo, tag)
if err == nil {
return r, nil
}
if resp == nil || resp.StatusCode != http.StatusNotFound {
return nil, err
}

id, parseErr := strconv.ParseInt(tag, 10, 64)
if parseErr != nil {
return nil, forge.ErrNotFound
}

r, resp, err = s.client.Repositories.GetRelease(ctx, owner, repo, id)
if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, forge.ErrNotFound
}
return nil, err
}
return r, nil
}

func (s *gitHubReleaseService) Get(ctx context.Context, owner, repo, tag string) (*forge.Release, error) {
r, err := s.getReleaseByTagOrID(ctx, owner, repo, tag)
if err != nil {
return nil, err
}
result := convertGitHubRelease(r)
return &result, nil
}
Expand Down Expand Up @@ -152,11 +176,8 @@ func (s *gitHubReleaseService) Create(ctx context.Context, owner, repo string, o

func (s *gitHubReleaseService) Update(ctx context.Context, owner, repo, tag string, opts forge.UpdateReleaseOpts) (*forge.Release, error) {
// Get existing release to find its ID
existing, resp, err := s.client.Repositories.GetReleaseByTag(ctx, owner, repo, tag)
existing, err := s.getReleaseByTagOrID(ctx, owner, repo, tag)
if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, forge.ErrNotFound
}
return nil, err
}

Expand Down Expand Up @@ -205,15 +226,12 @@ func (s *gitHubReleaseService) Update(ctx context.Context, owner, repo, tag stri
}

func (s *gitHubReleaseService) Delete(ctx context.Context, owner, repo, tag string) error {
existing, resp, err := s.client.Repositories.GetReleaseByTag(ctx, owner, repo, tag)
existing, err := s.getReleaseByTagOrID(ctx, owner, repo, tag)
if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
return forge.ErrNotFound
}
return err
}

resp, err = s.client.Repositories.DeleteRelease(ctx, owner, repo, existing.GetID())
resp, err := s.client.Repositories.DeleteRelease(ctx, owner, repo, existing.GetID())
if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
return forge.ErrNotFound
Expand All @@ -224,11 +242,8 @@ func (s *gitHubReleaseService) Delete(ctx context.Context, owner, repo, tag stri
}

func (s *gitHubReleaseService) UploadAsset(ctx context.Context, owner, repo, tag string, file *os.File) (*forge.ReleaseAsset, error) {
existing, resp, err := s.client.Repositories.GetReleaseByTag(ctx, owner, repo, tag)
existing, err := s.getReleaseByTagOrID(ctx, owner, repo, tag)
if err != nil {
if resp != nil && resp.StatusCode == http.StatusNotFound {
return nil, forge.ErrNotFound
}
return nil, err
}

Expand Down
Loading
Loading