package workflow import ( "errors" "sort" ) // errIncompatibleFlags 對應 gitea.py 的「--mine 與 --reviewer 不能併用」。 var errIncompatibleFlags = errors.New("--mine 與 --reviewer 不能併用") // Work 是 next 命令的輸出物件:Type 為 "pull" 或 "issue";Role 僅 pull 帶 // ("author" 或 "reviewer"),對應 gitea.py 的 _work_dict。 type Work struct { Type string `json:"type"` Role string `json:"role,omitempty"` Repo string `json:"repo"` Number int64 `json:"number"` Title string `json:"title"` URL string `json:"url"` } // issueNumber 回傳 issue 編號:number 缺漏時回退 index(gitea.py 語義)。 func (i *Issue) issueNumber() int64 { if i.Number != 0 { return i.Number } return i.Index } // Num 是 issueNumber 的公開介面(CLI 輸出用)。 func (i *Issue) Num() int64 { return i.issueNumber() } // Next 回傳下一件未完成工作:先自己發起的待跟進 PR(role=author),再待審核 // 的 PR(role=reviewer,含尚無留言),再分派給自己的 open issue。各層皆以 // 「最久未更新優先」排序後取第一件(gitea.py 的 find_next_work)。 // 沒有任何工作時回傳 nil。 func (s *Scanner) Next() (*Work, error) { username, err := s.Whoami() if err != nil { return nil, err } _ = username // 第一層:自己發起、最後一則留言不是自己的 open PR。 minePulls, err := s.Pulls(true, false) if err != nil { return nil, err } if len(minePulls) > 0 { sorted := sortPullItemsByUpdated(minePulls) return workFromPull(sorted[0], "author"), nil } // 第二層:自己是審核者、最後一則留言不是自己的 open PR。 reviewerPulls, err := s.Pulls(false, true) if err != nil { return nil, err } if len(reviewerPulls) > 0 { sorted := sortPullItemsByUpdated(reviewerPulls) return workFromPull(sorted[0], "reviewer"), nil } // 第三層:分派給自己、最後一則留言不是 assignee 的 open issue。 issues, err := s.Mine() if err != nil { return nil, err } if len(issues) > 0 { sorted := sortIssuesByUpdated(issues) return workFromIssue(&sorted[0]), nil } return nil, nil } // sortPullItemsByUpdated 依 updatedKey 升冪(最久未更新在前)穩定排序。 func sortPullItemsByUpdated(items []PullItem) []PullItem { out := append([]PullItem(nil), items...) sort.SliceStable(out, func(a, b int) bool { return out[a].Pull.updatedKey() < out[b].Pull.updatedKey() }) return out } // sortIssuesByUpdated 依 updatedKey 升冪(最久未更新在前)穩定排序。 func sortIssuesByUpdated(items []Issue) []Issue { out := append([]Issue(nil), items...) sort.SliceStable(out, func(a, b int) bool { return out[a].updatedKey() < out[b].updatedKey() }) return out } // workFromPull 由 PR 組出 Work 物件。 func workFromPull(item PullItem, role string) *Work { return &Work{ Type: "pull", Role: role, Repo: item.Owner + "/" + item.Repo, Number: item.Pull.Number, Title: item.Pull.Title, URL: item.Pull.WebURL(), } } // workFromIssue 由 issue 組出 Work 物件。 func workFromIssue(issue *Issue) *Work { return &Work{ Type: "issue", Repo: issue.FullName(), Number: issue.issueNumber(), Title: issue.Title, URL: issue.WebURL(), } }