From 18f22166d8f1af3823b75e7806262058fbe2df8f Mon Sep 17 00:00:00 2001 From: Muneerali199 Date: Wed, 10 Jun 2026 00:26:40 +0530 Subject: [PATCH 1/4] Add ExcludeLabels support to IssueListByRepoOptions --- github/issues.go | 4 ++++ github/issues_test.go | 22 ++++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/github/issues.go b/github/issues.go index bea89912b5a..1fb16d21321 100644 --- a/github/issues.go +++ b/github/issues.go @@ -364,6 +364,10 @@ type IssueListByRepoOptions struct { // Labels filters issues based on their label. Labels []string `url:"labels,omitempty,comma"` + // ExcludeLabels filters issues to exclude those with the specified labels. + // This is an undocumented GitHub API feature. + ExcludeLabels []string `url:"-labels,omitempty,comma"` + // Sort specifies how to sort issues. Possible values are: created, updated, // and comments. Default value is "created". Sort string `url:"sort,omitempty"` diff --git a/github/issues_test.go b/github/issues_test.go index e4da6c72c49..7a02d9600ed 100644 --- a/github/issues_test.go +++ b/github/issues_test.go @@ -187,16 +187,17 @@ func TestIssuesService_ListByRepo(t *testing.T) { testMethod(t, r, "GET") testHeader(t, r, "Accept", mediaTypeReactionsPreview) testFormValues(t, r, values{ - "milestone": "*", - "state": "closed", - "assignee": "a", - "creator": "c", - "mentioned": "m", - "labels": "a,b", - "sort": "updated", - "direction": "asc", - "since": referenceTime.Format(time.RFC3339), - "per_page": "1", + "milestone": "*", + "state": "closed", + "assignee": "a", + "creator": "c", + "mentioned": "m", + "labels": "a,b", + "-labels": "c,d", + "sort": "updated", + "direction": "asc", + "since": referenceTime.Format(time.RFC3339), + "per_page": "1", }) fmt.Fprint(w, `[{"number":1}]`) }) @@ -209,6 +210,7 @@ func TestIssuesService_ListByRepo(t *testing.T) { Creator: "c", Mentioned: "m", Labels: []string{"a", "b"}, + ExcludeLabels: []string{"c", "d"}, Sort: "updated", Direction: "asc", Since: referenceTime, From 9c30ff0996bf42d968d11282be7ee9864d11f59d Mon Sep 17 00:00:00 2001 From: Muneerali199 Date: Wed, 10 Jun 2026 01:28:59 +0530 Subject: [PATCH 2/4] Run fmt.sh, generate.sh, test.sh, lint.sh and fix lint issues - Run generate.sh to generate GetExcludeLabels accessor - Run fmt.sh to fix gci formatting in test file - Add structfield exception for ExcludeLabels field - All tests pass and lint reports 0 issues --- .golangci.yml | 1 + github/github-accessors.go | 8 ++++++++ github/github-accessors_test.go | 11 +++++++++++ github/issues_test.go | 22 +++++++++++----------- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index a2fea56937c..1fde92e18a3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -266,6 +266,7 @@ linters: - Tree.Entries - User.LdapDn # TODO: LDAPDN - UsersSearchResult.Total + - IssueListByRepoOptions.ExcludeLabels # url:"-labels" can't match "Labels" (already taken) - UsersSearchResult.Users - WeeklyStats.Additions - WeeklyStats.Commits diff --git a/github/github-accessors.go b/github/github-accessors.go index bc3194bc1b6..29c68fc8518 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -20078,6 +20078,14 @@ func (i *IssueListByRepoOptions) GetDirection() string { return i.Direction } +// GetExcludeLabels returns the ExcludeLabels slice if it's non-nil, nil otherwise. +func (i *IssueListByRepoOptions) GetExcludeLabels() []string { + if i == nil || i.ExcludeLabels == nil { + return nil + } + return i.ExcludeLabels +} + // GetLabels returns the Labels slice if it's non-nil, nil otherwise. func (i *IssueListByRepoOptions) GetLabels() []string { if i == nil || i.Labels == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index c9ced173fe0..b944266f2b7 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -25392,6 +25392,17 @@ func TestIssueListByRepoOptions_GetDirection(tt *testing.T) { i.GetDirection() } +func TestIssueListByRepoOptions_GetExcludeLabels(tt *testing.T) { + tt.Parallel() + zeroValue := []string{} + i := &IssueListByRepoOptions{ExcludeLabels: zeroValue} + i.GetExcludeLabels() + i = &IssueListByRepoOptions{} + i.GetExcludeLabels() + i = nil + i.GetExcludeLabels() +} + func TestIssueListByRepoOptions_GetLabels(tt *testing.T) { tt.Parallel() zeroValue := []string{} diff --git a/github/issues_test.go b/github/issues_test.go index 7a02d9600ed..99c87e31522 100644 --- a/github/issues_test.go +++ b/github/issues_test.go @@ -187,17 +187,17 @@ func TestIssuesService_ListByRepo(t *testing.T) { testMethod(t, r, "GET") testHeader(t, r, "Accept", mediaTypeReactionsPreview) testFormValues(t, r, values{ - "milestone": "*", - "state": "closed", - "assignee": "a", - "creator": "c", - "mentioned": "m", - "labels": "a,b", - "-labels": "c,d", - "sort": "updated", - "direction": "asc", - "since": referenceTime.Format(time.RFC3339), - "per_page": "1", + "milestone": "*", + "state": "closed", + "assignee": "a", + "creator": "c", + "mentioned": "m", + "labels": "a,b", + "-labels": "c,d", + "sort": "updated", + "direction": "asc", + "since": referenceTime.Format(time.RFC3339), + "per_page": "1", }) fmt.Fprint(w, `[{"number":1}]`) }) From 08da79d0e07a7635b8e1d4bcd649b7c7df03383f Mon Sep 17 00:00:00 2001 From: Muneerali199 Date: Wed, 10 Jun 2026 02:00:14 +0530 Subject: [PATCH 3/4] Sort IssueListByRepoOptions.ExcludeLabels entry alphabetically in allowed-tag-names --- .golangci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 1fde92e18a3..b896321aebe 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -228,6 +228,7 @@ linters: - HookResponse.RawPayload - Issue.PullRequestLinks # TODO: PullRequest - IssueImportRequest.IssueImport # TODO: Issue + - IssueListByRepoOptions.ExcludeLabels # url:"-labels" can't match "Labels" (already taken) - IssuesSearchResult.Issues # TODO: Items - IssuesSearchResult.Total - LabelsSearchResult.Labels # TODO: Items @@ -266,7 +267,6 @@ linters: - Tree.Entries - User.LdapDn # TODO: LDAPDN - UsersSearchResult.Total - - IssueListByRepoOptions.ExcludeLabels # url:"-labels" can't match "Labels" (already taken) - UsersSearchResult.Users - WeeklyStats.Additions - WeeklyStats.Commits From 420eed4e55c8c24ad47b7b5bb8a36dee3b6ecf63 Mon Sep 17 00:00:00 2001 From: Muneerali199 Date: Thu, 11 Jun 2026 22:56:07 +0530 Subject: [PATCH 4/4] Switch ExcludeLabels to client-side filtering The GitHub Issues REST API does not support the undocumented -labels query parameter. Switch to client-side filtering: fetch issues normally with Labels filter, then filter out ExcludeLabels matches in Go code. - Change url tag from "-labels,omitempty,comma" to "-" (skip query param) - Add client-side filtering logic in ListByRepo - Add test for client-side filtering behavior Signed-off-by: Muneerali199 --- .golangci.yml | 2 +- github/issues.go | 30 ++++++++++++++++++++++++++-- github/issues_test.go | 46 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b896321aebe..e3537546387 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -228,7 +228,7 @@ linters: - HookResponse.RawPayload - Issue.PullRequestLinks # TODO: PullRequest - IssueImportRequest.IssueImport # TODO: Issue - - IssueListByRepoOptions.ExcludeLabels # url:"-labels" can't match "Labels" (already taken) + - IssueListByRepoOptions.ExcludeLabels # url:"-" skips query param; no matching tag - IssuesSearchResult.Issues # TODO: Items - IssuesSearchResult.Total - LabelsSearchResult.Labels # TODO: Items diff --git a/github/issues.go b/github/issues.go index 1fb16d21321..28fe4c01e84 100644 --- a/github/issues.go +++ b/github/issues.go @@ -8,6 +8,7 @@ package github import ( "context" "fmt" + "strings" "time" ) @@ -365,8 +366,9 @@ type IssueListByRepoOptions struct { Labels []string `url:"labels,omitempty,comma"` // ExcludeLabels filters issues to exclude those with the specified labels. - // This is an undocumented GitHub API feature. - ExcludeLabels []string `url:"-labels,omitempty,comma"` + // Filtering is done client-side after fetching, since GitHub's Issues REST API + // does not support server-side label exclusion. + ExcludeLabels []string `url:"-"` // Sort specifies how to sort issues. Possible values are: created, updated, // and comments. Default value is "created". @@ -412,6 +414,30 @@ func (s *IssuesService) ListByRepo(ctx context.Context, owner, repo string, opts return nil, resp, err } + // Filter out issues with excluded labels client-side. + // The GitHub Issues REST API does not support server-side label exclusion, + // so we apply the filter in-memory after fetching results. + if len(opts.ExcludeLabels) > 0 { + exclude := make(map[string]bool, len(opts.ExcludeLabels)) + for _, l := range opts.ExcludeLabels { + exclude[strings.ToLower(l)] = true + } + filtered := make([]*Issue, 0, len(issues)) + for _, issue := range issues { + shouldExclude := false + for _, label := range issue.Labels { + if exclude[strings.ToLower(label.GetName())] { + shouldExclude = true + break + } + } + if !shouldExclude { + filtered = append(filtered, issue) + } + } + issues = filtered + } + return issues, resp, nil } diff --git a/github/issues_test.go b/github/issues_test.go index 99c87e31522..fe386db42ec 100644 --- a/github/issues_test.go +++ b/github/issues_test.go @@ -192,9 +192,8 @@ func TestIssuesService_ListByRepo(t *testing.T) { "assignee": "a", "creator": "c", "mentioned": "m", - "labels": "a,b", - "-labels": "c,d", - "sort": "updated", + "labels": "a,b", + "sort": "updated", "direction": "asc", "since": referenceTime.Format(time.RFC3339), "per_page": "1", @@ -243,6 +242,47 @@ func TestIssuesService_ListByRepo(t *testing.T) { }) } +func TestIssuesService_ListByRepo_ExcludeLabels(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeReactionsPreview) + // ExcludeLabels should NOT be sent as a query parameter. + testFormValues(t, r, values{ + "labels": "bug", + }) + // Server returns all bug-labeled issues; client-side filtering handles exclusion. + fmt.Fprint(w, `[ + {"number":1, "labels":[{"name":"bug"}]}, + {"number":2, "labels":[{"name":"bug"},{"name":"wontfix"}]}, + {"number":3, "labels":[{"name":"bug"},{"name":"enhancement"}]}, + {"number":4, "labels":[{"name":"bug"},{"name":"wontfix"},{"name":"duplicate"}]} + ]`) + }) + + opt := &IssueListByRepoOptions{ + Labels: []string{"bug"}, + ExcludeLabels: []string{"wontfix", "duplicate"}, + } + + ctx := t.Context() + issues, _, err := client.Issues.ListByRepo(ctx, "o", "r", opt) + if err != nil { + t.Errorf("Issues.ListByRepo returned error: %v", err) + } + + // Issues #1 and #3 should remain; #2 and #4 should be excluded by client-side filter. + want := []*Issue{ + {Number: Ptr(1), Labels: []*Label{{Name: Ptr("bug")}}}, + {Number: Ptr(3), Labels: []*Label{{Name: Ptr("bug")}, {Name: Ptr("enhancement")}}}, + } + if !cmp.Equal(issues, want) { + t.Errorf("Issues.ListByRepo returned %+v, want %+v", issues, want) + } +} + func TestIssuesService_Get(t *testing.T) { t.Parallel() client, mux, _ := setup(t)