diff --git a/services/migrations/gitea_downloader.go b/services/migrations/gitea_downloader.go index 6ec70e2bbb0..60bff07434b 100644 --- a/services/migrations/gitea_downloader.go +++ b/services/migrations/gitea_downloader.go @@ -13,6 +13,7 @@ import ( "strings" "time" + "gitea.dev/modules/container" "gitea.dev/modules/log" base "gitea.dev/modules/migration" "gitea.dev/modules/structs" @@ -472,6 +473,7 @@ func (g *GiteaDownloader) GetIssues(ctx context.Context, page, perPage int) ([]* // GetComments returns comments according issueNumber func (g *GiteaDownloader) GetComments(ctx context.Context, commentable base.Commentable) ([]*base.Comment, bool, error) { allComments := make([]*base.Comment, 0, g.maxPerPage) + seenIDs := container.Set[int64]{} for i := 1; ; i++ { // make sure gitea can shutdown gracefully @@ -488,8 +490,12 @@ func (g *GiteaDownloader) GetComments(ctx context.Context, commentable base.Comm if err != nil { return nil, false, fmt.Errorf("error while listing comments for issue #%d. Error: %w", commentable.GetForeignIndex(), err) } + if len(comments) == 0 || seenIDs.Contains(comments[0].ID) { + break // the endpoint ignores page and limit, so page 2 repeats page 1 + } for _, comment := range comments { + seenIDs.Add(comment.ID) reactions, err := g.getCommentReactions(comment.ID) if err != nil { WarnAndNotice("Unable to load comment reactions during migrating issue #%d for comment %d in %s. Error: %v", commentable.GetForeignIndex(), comment.ID, g, err) @@ -508,7 +514,7 @@ func (g *GiteaDownloader) GetComments(ctx context.Context, commentable base.Comm }) } - if !g.pagination || len(comments) < g.maxPerPage { + if !g.pagination || len(comments) != g.maxPerPage { break } } diff --git a/services/migrations/gitea_downloader_test.go b/services/migrations/gitea_downloader_test.go index e65aa024ee2..4b1dbdf22bd 100644 --- a/services/migrations/gitea_downloader_test.go +++ b/services/migrations/gitea_downloader_test.go @@ -4,10 +4,15 @@ package migrations import ( + "fmt" + "net/http" + "net/http/httptest" "os" "path/filepath" "runtime" "sort" + "strconv" + "strings" "testing" "time" @@ -309,3 +314,49 @@ func TestGiteaDownloadRepo(t *testing.T) { }, }, reviews) } + +func TestGiteaDownloadCommentsPaging(t *testing.T) { + for _, tc := range []struct { + maxResponseItems, commentCount, requests int + paginated bool + }{ + {maxResponseItems: 2, commentCount: 2, requests: 2}, + {maxResponseItems: 2, commentCount: 3, requests: 1}, + {maxResponseItems: 2, commentCount: 4, requests: 3, paginated: true}, + {maxResponseItems: 0, commentCount: 0, requests: 1}, + } { + t.Run(strconv.Itoa(tc.commentCount), func(t *testing.T) { + commentRequests := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v1/version": + _, _ = w.Write([]byte(`{"version":"1.27.0"}`)) + case "/api/v1/settings/api": + _, _ = fmt.Fprintf(w, `{"max_response_items":%d}`, tc.maxResponseItems) + case "/api/v1/repos/o/r/issues/1/comments": + commentRequests++ + comments := make([]string, tc.commentCount) + for i := range comments { + comments[i] = fmt.Sprintf(`{"id":%d,"user":{}}`, i+1) + } + if tc.paginated { + page, _ := strconv.Atoi(r.URL.Query().Get("page")) + comments = comments[(page-1)*tc.maxResponseItems : min(page*tc.maxResponseItems, len(comments))] + } + _, _ = w.Write([]byte("[" + strings.Join(comments, ",") + "]")) + default: + _, _ = w.Write([]byte(`[]`)) + } + })) + defer server.Close() + + downloader, err := NewGiteaDownloader(t.Context(), server.URL, "o/r", "", "", "") + require.NoError(t, err) + + comments, _, err := downloader.GetComments(t.Context(), &base.Issue{Number: 1}) + require.NoError(t, err) + assert.Len(t, comments, tc.commentCount) + assert.Equal(t, tc.requests, commentRequests) + }) + } +}