Commit 480aeee73f

480aeee73f37d273e63517d0329b9f3a00899722

parent: 314c9ca55c

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-11 16:17 UTC

store, web: the issue list shows the org's labels

ListIssueLabels filtered on repo_id, which no org label has, so a chip
set from an org label never reached the web issue list. It takes the
repository and the scope clause now, and joins issues so the answer stays
the repository's own.

Ref #203
internal/httpd/web.go +1 −1
@@ -1668,7 +1668,7 @@ func (s *Server) issues(w http.ResponseWriter, r *http.Request) {
16681668 issues = issues[:listPage]
16691669 older = olderLink(r, issues[len(issues)-1].Number)
16701670 }
1671 if labels, err := s.st.ListIssueLabels(p.Repo.ID); err == nil {
1671 if labels, err := s.st.ListIssueLabels(p.Repo); err == nil {
16721672 for i := range issues {
16731673 issues[i].Labels = labels[issues[i].ID]
16741674 }
internal/store/issues.go +7 −4
@@ -271,13 +271,16 @@ func (s *Store) AddIssueSystemComment(issueID, actorID int64, body string) error
271271}
272272
273273// ListIssueLabels returns the label names attached to each issue of a
274// repo, keyed by issue id. Used by the web issue listing; ListIssues
275// itself stays label-free for the CLI's lean list output.
276func (s *Store) ListIssueLabels(repoID int64) (map[int64][]string, error) {
274// repo, keyed by issue id, its org's labels included. Used by the web
275// issue listing; ListIssues itself stays label-free for the CLI's lean
276// list output.
277func (s *Store) ListIssueLabels(repo Repo) (map[int64][]string, error) {
278 where, args := scopeClause("l", repo)
277279 rows, err := s.DB.Query(`
278280 SELECT il.issue_id, l.name FROM issue_labels il
279281 JOIN labels l ON l.id = il.label_id
280 WHERE l.repo_id = ? ORDER BY l.name`, repoID)
282 JOIN issues i ON i.id = il.issue_id
283 WHERE i.repo_id = ? AND `+where+` ORDER BY l.name`, append([]any{repo.ID}, args...)...)
281284 if err != nil {
282285 return nil, err
283286 }
internal/store/labels_test.go +25
@@ -137,6 +137,31 @@ func TestIssueLabelResolvesOrgRowFirst(t *testing.T) {
137137 }
138138}
139139
140// The web issue list reads labels per repository; an org label attached
141// to an issue has to come back from there like the repository's own.
142func TestListIssueLabelsIncludesOrgRows(t *testing.T) {
143 f := newAcme(t)
144 if _, err := f.s.SetOrgLabel(f.org, "bug", ""); err != nil {
145 t.Fatal(err)
146 }
147 if err := f.s.SetLabel(f.core, "docs", ""); err != nil {
148 t.Fatal(err)
149 }
150 for _, name := range []string{"bug", "docs"} {
151 if err := f.s.SetIssueLabel(f.core, f.coreIssue, name, true); err != nil {
152 t.Fatal(err)
153 }
154 }
155 got, err := f.s.ListIssueLabels(f.core)
156 if err != nil || len(got[f.coreIssue]) != 2 || got[f.coreIssue][0] != "bug" || got[f.coreIssue][1] != "docs" {
157 t.Fatalf("core issue labels = %v, %v", got, err)
158 }
159 // Another repository under the org does not pick up core's attachment.
160 if got, _ := f.s.ListIssueLabels(f.site); len(got) != 0 {
161 t.Fatalf("site issue labels = %v", got)
162 }
163}
164
140165func TestRepoLabelRefusedWhenOrgHoldsName(t *testing.T) {
141166 f := newAcme(t)
142167 if _, err := f.s.SetOrgLabel(f.org, "bug", ""); err != nil {