Skip to content

Commit 42cfcec

Browse files
author
Alex
committed
fix(robot): code review fixes for skip_in_progress filtering
- Remove duplicate issues_model import alias (F6) - Compile task branch regex once at package level (F7) - Replace N+1 per-branch queries with batch getIssueIDsByIndices (F8) - Remove redundant is_closed filter from batch lookup (F9) - Add repo_id JOIN filter to getIssueIDsByLabel (F10) - Only swallow ErrRepoLabelNotExist, propagate real DB errors (F11) - Strengthen integration test assertions (F12) Refs terraphim/gitea-robot#20, terraphim/gitea#41
1 parent 366bea3 commit 42cfcec

2 files changed

Lines changed: 72 additions & 31 deletions

File tree

routers/api/v1/robot/ready_graph.go

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"code.gitea.io/gitea/models/db"
1313
git_model "code.gitea.io/gitea/models/git"
1414
"code.gitea.io/gitea/models/issues"
15-
issues_model "code.gitea.io/gitea/models/issues"
1615
"code.gitea.io/gitea/models/repo"
1716
"code.gitea.io/gitea/modules/log"
1817
"code.gitea.io/gitea/modules/optional"
@@ -41,6 +40,9 @@ type ReadyResponse struct {
4140
ReadyIssues []ReadyIssue `json:"ready_issues"`
4241
}
4342

43+
// taskBranchRe matches task/<IDX>-* branch naming convention (e.g., task/69-ci)
44+
var taskBranchRe = regexp.MustCompile(`^task/(\d+)-`)
45+
4446
// Ready returns issues that are ready to be worked on (no blocking dependencies)
4547
func Ready(ctx *context.APIContext) {
4648
// 1. Check feature enabled
@@ -300,29 +302,35 @@ func getInProgressIssues(ctx *context.APIContext, repository *repo.Repository) (
300302
return inProgress, nil
301303
}
302304

303-
// findInProgressByBranches finds issues that have a branch matching task/<IDX>-* pattern
305+
// findInProgressByBranches finds issues that have a branch matching task/<IDX>-* pattern.
306+
// Uses a single batch query instead of N+1 per-branch lookups.
304307
func findInProgressByBranches(ctx *context.APIContext, repository *repo.Repository, inProgress map[int64]bool) error {
305-
// Get all non-deleted branches for this repo
306308
branches, err := git_model.GetBranches(ctx, repository.ID, nil, false)
307309
if err != nil {
308310
return err
309311
}
310312

311-
// Pattern to match task/<IDX>-* e.g., task/69-ci
312-
pattern := regexp.MustCompile(`^task/(\d+)-`)
313-
313+
var indices []string
314314
for _, branch := range branches {
315-
matches := pattern.FindStringSubmatch(branch.Name)
315+
matches := taskBranchRe.FindStringSubmatch(branch.Name)
316316
if len(matches) >= 2 {
317-
issueIndex := matches[1]
318-
// We need to find the issue with this index
319-
issueID, err := getIssueIDByIndex(ctx, repository.ID, issueIndex)
320-
if err == nil && issueID > 0 {
321-
inProgress[issueID] = true
322-
}
317+
indices = append(indices, matches[1])
323318
}
324319
}
325320

321+
if len(indices) == 0 {
322+
return nil
323+
}
324+
325+
issueIDs, err := getIssueIDsByIndices(ctx, repository.ID, indices)
326+
if err != nil {
327+
return err
328+
}
329+
330+
for id := range issueIDs {
331+
inProgress[id] = true
332+
}
333+
326334
return nil
327335
}
328336

@@ -346,11 +354,12 @@ func findInProgressByPRs(ctx *context.APIContext, repository *repo.Repository, i
346354

347355
// findInProgressByLabels finds issues with status/in-progress label
348356
func findInProgressByLabels(ctx *context.APIContext, repository *repo.Repository, inProgress map[int64]bool) error {
349-
// Get the status/in-progress label
350-
label, err := issues_model.GetLabelInRepoByName(ctx, repository.ID, "status/in-progress")
357+
label, err := issues.GetLabelInRepoByName(ctx, repository.ID, "status/in-progress")
351358
if err != nil {
352-
// Label doesn't exist, that's fine - no issues can have it
353-
return nil
359+
if issues.IsErrRepoLabelNotExist(err) {
360+
return nil
361+
}
362+
return err
354363
}
355364

356365
// Get all issues with this label
@@ -366,23 +375,48 @@ func findInProgressByLabels(ctx *context.APIContext, repository *repo.Repository
366375
return nil
367376
}
368377

369-
// getIssueIDByIndex returns the issue ID for a given repository and issue index
370-
func getIssueIDByIndex(ctx *context.APIContext, repoID int64, indexStr string) (int64, error) {
371-
var issueID int64
372-
_, err := db.GetEngine(ctx).
378+
// getIssueIDsByIndices returns a set of issue IDs for the given repo and issue indices.
379+
// Uses a single batch query to avoid N+1 lookups.
380+
// Does not filter by is_closed: the ready list only contains open issues,
381+
// so a branch for a closed issue is harmless (a stale branch).
382+
func getIssueIDsByIndices(ctx *context.APIContext, repoID int64, indices []string) (map[int64]bool, error) {
383+
result := make(map[int64]bool)
384+
if len(indices) == 0 {
385+
return result, nil
386+
}
387+
388+
type row struct {
389+
ID int64
390+
}
391+
392+
rows := make([]row, 0)
393+
sess := db.GetEngine(ctx).
373394
Table("issue").
374395
Select("id").
375-
Where("repo_id = ? AND index = ? AND is_closed = ? AND is_pull = ?", repoID, indexStr, false, false).
376-
Get(&issueID)
377-
if err != nil {
378-
return 0, err
396+
Where("repo_id = ? AND is_pull = ?", repoID, false).
397+
In("index", toAnySlice(indices))
398+
if err := sess.Find(&rows); err != nil {
399+
return nil, err
400+
}
401+
402+
for _, r := range rows {
403+
result[r.ID] = true
404+
}
405+
return result, nil
406+
}
407+
408+
// toAnySlice converts []string to []any for xorm In() clause
409+
func toAnySlice(strs []string) []any {
410+
out := make([]any, len(strs))
411+
for i, s := range strs {
412+
out[i] = s
379413
}
380-
return issueID, nil
414+
return out
381415
}
382416

383417
// getOpenPullRequestsForRepo returns all open pull requests for a repository
384-
func getOpenPullRequestsForRepo(ctx *context.APIContext, repoID int64) ([]*issues_model.PullRequest, error) {
385-
prs := make([]*issues_model.PullRequest, 0)
418+
func getOpenPullRequestsForRepo(ctx *context.APIContext, repoID int64) ([]*issues.PullRequest, error) {
419+
prs := make([]*issues.PullRequest, 0)
386420
err := db.GetEngine(ctx).
387421
Table("pull_request").
388422
Join("INNER", "issue", "pull_request.issue_id = issue.id").
@@ -391,13 +425,14 @@ func getOpenPullRequestsForRepo(ctx *context.APIContext, repoID int64) ([]*issue
391425
return prs, err
392426
}
393427

394-
// getIssueIDsByLabel returns all issue IDs that have the given label
428+
// getIssueIDsByLabel returns all issue IDs in the given repo that have the given label
395429
func getIssueIDsByLabel(ctx *context.APIContext, repoID, labelID int64) ([]int64, error) {
396430
issueIDs := make([]int64, 0)
397431
err := db.GetEngine(ctx).
398432
Table("issue_label").
399-
Select("issue_id").
400-
Where("label_id = ?", labelID).
433+
Select("issue_label.issue_id").
434+
Join("INNER", "issue", "issue_label.issue_id = issue.id").
435+
Where("issue_label.label_id = ? AND issue.repo_id = ?", labelID, repoID).
401436
Find(&issueIDs)
402437
return issueIDs, err
403438
}

tests/integration/robot_security_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ func TestRobotAPI_ReadyEndpoint_SkipInProgress(t *testing.T) {
193193
var result map[string]any
194194
DecodeJSON(t, resp, &result)
195195
assert.Contains(t, result, "repo_id")
196+
assert.Contains(t, result, "total_count")
197+
assert.Contains(t, result, "ready_issues")
198+
assert.Contains(t, result, "repo_name")
196199

197200
// Test ready endpoint with skip_in_progress=true
198201
req2 := NewRequestf(t, "GET", "/api/v1/robot/ready?owner=%s&repo=%s&skip_in_progress=true", owner.Name, repo.Name)
@@ -201,6 +204,9 @@ func TestRobotAPI_ReadyEndpoint_SkipInProgress(t *testing.T) {
201204
var result2 map[string]any
202205
DecodeJSON(t, resp2, &result2)
203206
assert.Contains(t, result2, "repo_id")
207+
assert.Contains(t, result2, "total_count")
208+
assert.Contains(t, result2, "ready_issues")
209+
assert.Contains(t, result2, "repo_name")
204210
}
205211

206212
// TestRobotAPI_GraphEndpoint tests the /robot/graph endpoint

0 commit comments

Comments
 (0)