From 099df9949438856907a07adfdafa52b79f5b25bb Mon Sep 17 00:00:00 2001 From: Anay Garodia <76836303+AnayGarodia@users.noreply.github.com> Date: Mon, 20 Jul 2026 00:56:25 -0700 Subject: [PATCH] fix(github): resolve tag pagination loop in release hook (#6870) --- server/forge/github/github.go | 8 +++-- server/forge/github/github_test.go | 54 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/server/forge/github/github.go b/server/forge/github/github.go index dcebe8945..a02f111dd 100644 --- a/server/forge/github/github.go +++ b/server/forge/github/github.go @@ -787,10 +787,10 @@ func (c *client) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName return "", err } - page := 1 + opts := &github.ListOptions{Page: 1} var tag *github.RepositoryTag - for { - tags, _, err := gh.Repositories.ListTags(ctx, repo.Owner, repo.Name, &github.ListOptions{Page: page}) + for opts.Page > 0 { + tags, resp, err := gh.Repositories.ListTags(ctx, repo.Owner, repo.Name, opts) if err != nil { return "", err } @@ -804,6 +804,8 @@ func (c *client) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName if tag != nil { break } + + opts.Page = resp.NextPage } if tag == nil { return "", fmt.Errorf("could not find tag %s", tagName) diff --git a/server/forge/github/github_test.go b/server/forge/github/github_test.go index 595d19b7a..4ab6c1b72 100644 --- a/server/forge/github/github_test.go +++ b/server/forge/github/github_test.go @@ -325,3 +325,57 @@ func TestHook(t *testing.T) { assert.Empty(t, pipeline.ChangedFiles) }) } + +func TestGetTagCommitSHA(t *testing.T) { + // Tags API paginates 30 per page; put the target tag on the second page + // to exercise pagination instead of a first-page match. + mockedHTTPClient := github_mock.NewMockedHTTPClient( + github_mock.WithRequestMatchPages( + github_mock.GetReposTagsByOwnerByRepo, + []github.RepositoryTag{ + {Name: github.Ptr("v1.0.0")}, + {Name: github.Ptr("v1.0.1")}, + }, + []github.RepositoryTag{ + {Name: github.Ptr("v1.0.2")}, + { + Name: github.Ptr("v1.0.3"), + Commit: &github.Commit{SHA: github.Ptr("deadbeefcafe")}, + }, + }, + ), + ) + + gh, err := github.NewClient(github.WithHTTPClient(mockedHTTPClient)) + require.NoError(t, err) + + ctx := context.WithValue(context.Background(), githubClientKey, gh) + + mockStore := store_mocks.NewMockStore(t) + mockStore.On("GetUser", mock.Anything).Return(&model.User{ + ID: 1, + Login: "6543", + AccessToken: "token", + }, nil) + mockStore.On("GetRepoNameFallback", mock.Anything, mock.Anything, mock.Anything).Return(&model.Repo{ + ID: 1, + ForgeRemoteID: "1", + Owner: "6543", + Name: "hello-world", + UserID: 1, + }, nil) + ctx = store.InjectToContext(ctx, mockStore) + + c := &client{API: defaultAPI, url: defaultURL} + + t.Run("finds a tag beyond the first page", func(t *testing.T) { + sha, err := c.getTagCommitSHA(ctx, &model.Repo{ForgeRemoteID: "1", FullName: "6543/hello-world"}, "v1.0.3") + require.NoError(t, err) + assert.Equal(t, "deadbeefcafe", sha) + }) + + t.Run("returns an error instead of looping forever when the tag does not exist", func(t *testing.T) { + _, err := c.getTagCommitSHA(ctx, &model.Repo{ForgeRemoteID: "1", FullName: "6543/hello-world"}, "does-not-exist") + require.Error(t, err) + }) +}