From 752e45431d8841f8ed4b4fc8b5c9140646f7b000 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 2 Apr 2026 11:27:04 +0100 Subject: [PATCH 01/15] feat: Add Issue Dependencies API support Add four new methods to IssuesService for the Issue Dependencies REST API (apiVersion 2026-03-10): - ListBlockedBy: list dependencies blocking an issue - AddBlockedBy: add a blocking dependency to an issue - RemoveBlockedBy: remove a blocking dependency - ListBlocking: list issues that an issue is blocking Includes IssueDependencyRequest type, full test coverage with testBadOptions, testNewRequestAndDoFailure, testURLParseError, and testJSONMarshal helpers. --- github/issues_dependencies.go | 110 +++++++++++++++ github/issues_dependencies_test.go | 220 +++++++++++++++++++++++++++++ 2 files changed, 330 insertions(+) create mode 100644 github/issues_dependencies.go create mode 100644 github/issues_dependencies_test.go diff --git a/github/issues_dependencies.go b/github/issues_dependencies.go new file mode 100644 index 00000000000..18ee754b0ce --- /dev/null +++ b/github/issues_dependencies.go @@ -0,0 +1,110 @@ +// Copyright 2026 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// IssueDependencyRequest represents a request to add a dependency to an issue. +type IssueDependencyRequest struct { + IssueID *int64 `json:"issue_id,omitempty"` +} + +// ListBlockedBy lists the dependencies that block the specified issue. +// +// GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#list-dependencies-an-issue-is-blocked-by +// +//meta:operation GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by +func (s *IssuesService) ListBlockedBy(ctx context.Context, owner, repo string, number int, opts *ListOptions) ([]*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by", owner, repo, number) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var issues []*Issue + resp, err := s.client.Do(ctx, req, &issues) + if err != nil { + return nil, resp, err + } + + return issues, resp, nil +} + +// AddBlockedBy adds a "blocked by" dependency to the specified issue. +// +// GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#add-a-dependency-an-issue-is-blocked-by +// +//meta:operation POST /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by +func (s *IssuesService) AddBlockedBy(ctx context.Context, owner, repo string, number int, issueDepReq *IssueDependencyRequest) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by", owner, repo, number) + req, err := s.client.NewRequest("POST", u, issueDepReq) + if err != nil { + return nil, nil, err + } + + var issue *Issue + resp, err := s.client.Do(ctx, req, &issue) + if err != nil { + return nil, resp, err + } + + return issue, resp, nil +} + +// RemoveBlockedBy removes a "blocked by" dependency from the specified issue. +// +// GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#remove-dependency-an-issue-is-blocked-by +// +//meta:operation DELETE /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by/{issue_id} +func (s *IssuesService) RemoveBlockedBy(ctx context.Context, owner, repo string, number int, issueID int64) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by/%v", owner, repo, number, issueID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, nil, err + } + + var issue *Issue + resp, err := s.client.Do(ctx, req, &issue) + if err != nil { + return nil, resp, err + } + + return issue, resp, nil +} + +// ListBlocking lists the issues that the specified issue is blocking. +// +// GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#list-dependencies-an-issue-is-blocking +// +//meta:operation GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocking +func (s *IssuesService) ListBlocking(ctx context.Context, owner, repo string, number int, opts *ListOptions) ([]*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocking", owner, repo, number) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var issues []*Issue + resp, err := s.client.Do(ctx, req, &issues) + if err != nil { + return nil, resp, err + } + + return issues, resp, nil +} diff --git a/github/issues_dependencies_test.go b/github/issues_dependencies_test.go new file mode 100644 index 00000000000..0c44b3f72e4 --- /dev/null +++ b/github/issues_dependencies_test.go @@ -0,0 +1,220 @@ +// Copyright 2026 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestIssuesService_ListBlockedBy(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/repos/o/r/issues/1/dependencies/blocked_by", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"number":1347,"title":"Found a bug"}]`) + }) + + opt := &ListOptions{Page: 2} + ctx := t.Context() + issues, _, err := client.Issues.ListBlockedBy(ctx, "o", "r", 1, opt) + if err != nil { + t.Errorf("Issues.ListBlockedBy returned error: %v", err) + } + + want := []*Issue{{Number: Ptr(1347), Title: Ptr("Found a bug")}} + if !cmp.Equal(issues, want) { + t.Errorf("Issues.ListBlockedBy returned %+v, want %+v", issues, want) + } + + const methodName = "ListBlockedBy" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Issues.ListBlockedBy(ctx, "\n", "\n", -1, opt) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Issues.ListBlockedBy(ctx, "o", "r", 1, opt) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestIssuesService_ListBlockedBy_invalidOwner(t *testing.T) { + t.Parallel() + client, _, _ := setup(t) + + ctx := t.Context() + _, _, err := client.Issues.ListBlockedBy(ctx, "%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_AddBlockedBy(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + input := &IssueDependencyRequest{IssueID: Ptr(int64(42))} + + mux.HandleFunc("/repos/o/r/issues/1/dependencies/blocked_by", func(w http.ResponseWriter, r *http.Request) { + var v *IssueDependencyRequest + assertNilError(t, json.NewDecoder(r.Body).Decode(&v)) + + testMethod(t, r, "POST") + if !cmp.Equal(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"number":42,"title":"Dependency issue"}`) + }) + + ctx := t.Context() + issue, _, err := client.Issues.AddBlockedBy(ctx, "o", "r", 1, input) + if err != nil { + t.Errorf("Issues.AddBlockedBy returned error: %v", err) + } + + want := &Issue{Number: Ptr(42), Title: Ptr("Dependency issue")} + if !cmp.Equal(issue, want) { + t.Errorf("Issues.AddBlockedBy returned %+v, want %+v", issue, want) + } + + const methodName = "AddBlockedBy" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Issues.AddBlockedBy(ctx, "\n", "\n", -1, input) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Issues.AddBlockedBy(ctx, "o", "r", 1, input) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestIssuesService_AddBlockedBy_invalidOwner(t *testing.T) { + t.Parallel() + client, _, _ := setup(t) + + ctx := t.Context() + _, _, err := client.Issues.AddBlockedBy(ctx, "%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_RemoveBlockedBy(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/repos/o/r/issues/1/dependencies/blocked_by/42", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + fmt.Fprint(w, `{"number":1,"title":"Original issue"}`) + }) + + ctx := t.Context() + issue, _, err := client.Issues.RemoveBlockedBy(ctx, "o", "r", 1, 42) + if err != nil { + t.Errorf("Issues.RemoveBlockedBy returned error: %v", err) + } + + want := &Issue{Number: Ptr(1), Title: Ptr("Original issue")} + if !cmp.Equal(issue, want) { + t.Errorf("Issues.RemoveBlockedBy returned %+v, want %+v", issue, want) + } + + const methodName = "RemoveBlockedBy" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Issues.RemoveBlockedBy(ctx, "\n", "\n", -1, 42) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Issues.RemoveBlockedBy(ctx, "o", "r", 1, 42) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestIssuesService_RemoveBlockedBy_invalidOwner(t *testing.T) { + t.Parallel() + client, _, _ := setup(t) + + ctx := t.Context() + _, _, err := client.Issues.RemoveBlockedBy(ctx, "%", "%", 1, 42) + testURLParseError(t, err) +} + +func TestIssuesService_ListBlocking(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/repos/o/r/issues/1/dependencies/blocking", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"number":1348,"title":"Blocked issue"}]`) + }) + + opt := &ListOptions{Page: 2} + ctx := t.Context() + issues, _, err := client.Issues.ListBlocking(ctx, "o", "r", 1, opt) + if err != nil { + t.Errorf("Issues.ListBlocking returned error: %v", err) + } + + want := []*Issue{{Number: Ptr(1348), Title: Ptr("Blocked issue")}} + if !cmp.Equal(issues, want) { + t.Errorf("Issues.ListBlocking returned %+v, want %+v", issues, want) + } + + const methodName = "ListBlocking" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Issues.ListBlocking(ctx, "\n", "\n", -1, opt) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Issues.ListBlocking(ctx, "o", "r", 1, opt) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestIssuesService_ListBlocking_invalidOwner(t *testing.T) { + t.Parallel() + client, _, _ := setup(t) + + ctx := t.Context() + _, _, err := client.Issues.ListBlocking(ctx, "%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssueDependencyRequest_Marshal(t *testing.T) { + t.Parallel() + testJSONMarshal(t, &IssueDependencyRequest{}, "{}") + + u := &IssueDependencyRequest{ + IssueID: Ptr(int64(1)), + } + + want := `{ + "issue_id": 1 + }` + + testJSONMarshal(t, u, want) +} From 9aba99564b4f8d7a3025cd5d3e22b6980ea02f93 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 2 Apr 2026 11:27:09 +0100 Subject: [PATCH 02/15] chore: Regenerate accessors and iterators Run go generate to add: - GetIssueID accessor for IssueDependencyRequest - ListBlockedBy and ListBlocking iterators with tests --- github/github-accessors.go | 8 ++ github/github-accessors_test.go | 11 +++ github/github-iterators.go | 62 ++++++++++++++ github/github-iterators_test.go | 144 ++++++++++++++++++++++++++++++++ 4 files changed, 225 insertions(+) diff --git a/github/github-accessors.go b/github/github-accessors.go index 37801159bdc..573442acde1 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -18166,6 +18166,14 @@ func (i *IssueCommentEvent) GetSender() *User { return i.Sender } +// GetIssueID returns the IssueID field if it's non-nil, zero value otherwise. +func (i *IssueDependencyRequest) GetIssueID() int64 { + if i == nil || i.IssueID == nil { + return 0 + } + return *i.IssueID +} + // GetAction returns the Action field. func (i *IssueEvent) GetAction() string { if i == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index b1dc2e00138..f1052ca039e 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -22982,6 +22982,17 @@ func TestIssueCommentEvent_GetSender(tt *testing.T) { i.GetSender() } +func TestIssueDependencyRequest_GetIssueID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + i := &IssueDependencyRequest{IssueID: &zeroValue} + i.GetIssueID() + i = &IssueDependencyRequest{} + i.GetIssueID() + i = nil + i.GetIssueID() +} + func TestIssueEvent_GetAction(tt *testing.T) { tt.Parallel() i := &IssueEvent{} diff --git a/github/github-iterators.go b/github/github-iterators.go index f33c9edc47c..b6670c8a6b6 100644 --- a/github/github-iterators.go +++ b/github/github-iterators.go @@ -3404,6 +3404,68 @@ func (s *IssuesService) ListAssigneesIter(ctx context.Context, owner string, rep } } +// ListBlockedByIter returns an iterator that paginates through all results of ListBlockedBy. +func (s *IssuesService) ListBlockedByIter(ctx context.Context, owner string, repo string, number int, opts *ListOptions) iter.Seq2[*Issue, error] { + return func(yield func(*Issue, error) bool) { + // Create a copy of opts to avoid mutating the caller's struct + if opts == nil { + opts = &ListOptions{} + } else { + opts = Ptr(*opts) + } + + for { + results, resp, err := s.ListBlockedBy(ctx, owner, repo, number, opts) + if err != nil { + yield(nil, err) + return + } + + for _, item := range results { + if !yield(item, nil) { + return + } + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + } +} + +// ListBlockingIter returns an iterator that paginates through all results of ListBlocking. +func (s *IssuesService) ListBlockingIter(ctx context.Context, owner string, repo string, number int, opts *ListOptions) iter.Seq2[*Issue, error] { + return func(yield func(*Issue, error) bool) { + // Create a copy of opts to avoid mutating the caller's struct + if opts == nil { + opts = &ListOptions{} + } else { + opts = Ptr(*opts) + } + + for { + results, resp, err := s.ListBlocking(ctx, owner, repo, number, opts) + if err != nil { + yield(nil, err) + return + } + + for _, item := range results { + if !yield(item, nil) { + return + } + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + } +} + // ListByOrgIter returns an iterator that paginates through all results of ListByOrg. func (s *IssuesService) ListByOrgIter(ctx context.Context, org string, opts *IssueListByOrgOptions) iter.Seq2[*Issue, error] { return func(yield func(*Issue, error) bool) { diff --git a/github/github-iterators_test.go b/github/github-iterators_test.go index c48f02bcefc..6d0ce281044 100644 --- a/github/github-iterators_test.go +++ b/github/github-iterators_test.go @@ -7359,6 +7359,150 @@ func TestIssuesService_ListAssigneesIter(t *testing.T) { } } +func TestIssuesService_ListBlockedByIter(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + var callNum int + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + callNum++ + switch callNum { + case 1: + w.Header().Set("Link", `; rel="next"`) + fmt.Fprint(w, `[{},{},{}]`) + case 2: + fmt.Fprint(w, `[{},{},{},{}]`) + case 3: + fmt.Fprint(w, `[{},{}]`) + case 4: + w.WriteHeader(http.StatusNotFound) + case 5: + fmt.Fprint(w, `[{},{}]`) + } + }) + + iter := client.Issues.ListBlockedByIter(t.Context(), "", "", 0, nil) + var gotItems int + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 7; gotItems != want { + t.Errorf("client.Issues.ListBlockedByIter call 1 got %v items; want %v", gotItems, want) + } + + opts := &ListOptions{} + iter = client.Issues.ListBlockedByIter(t.Context(), "", "", 0, opts) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 2; gotItems != want { + t.Errorf("client.Issues.ListBlockedByIter call 2 got %v items; want %v", gotItems, want) + } + + iter = client.Issues.ListBlockedByIter(t.Context(), "", "", 0, nil) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err == nil { + t.Error("expected error; got nil") + } + } + if gotItems != 1 { + t.Errorf("client.Issues.ListBlockedByIter call 3 got %v items; want 1 (an error)", gotItems) + } + + iter = client.Issues.ListBlockedByIter(t.Context(), "", "", 0, nil) + gotItems = 0 + iter(func(item *Issue, err error) bool { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + return false + }) + if gotItems != 1 { + t.Errorf("client.Issues.ListBlockedByIter call 4 got %v items; want 1 (an error)", gotItems) + } +} + +func TestIssuesService_ListBlockingIter(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + var callNum int + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + callNum++ + switch callNum { + case 1: + w.Header().Set("Link", `; rel="next"`) + fmt.Fprint(w, `[{},{},{}]`) + case 2: + fmt.Fprint(w, `[{},{},{},{}]`) + case 3: + fmt.Fprint(w, `[{},{}]`) + case 4: + w.WriteHeader(http.StatusNotFound) + case 5: + fmt.Fprint(w, `[{},{}]`) + } + }) + + iter := client.Issues.ListBlockingIter(t.Context(), "", "", 0, nil) + var gotItems int + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 7; gotItems != want { + t.Errorf("client.Issues.ListBlockingIter call 1 got %v items; want %v", gotItems, want) + } + + opts := &ListOptions{} + iter = client.Issues.ListBlockingIter(t.Context(), "", "", 0, opts) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 2; gotItems != want { + t.Errorf("client.Issues.ListBlockingIter call 2 got %v items; want %v", gotItems, want) + } + + iter = client.Issues.ListBlockingIter(t.Context(), "", "", 0, nil) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err == nil { + t.Error("expected error; got nil") + } + } + if gotItems != 1 { + t.Errorf("client.Issues.ListBlockingIter call 3 got %v items; want 1 (an error)", gotItems) + } + + iter = client.Issues.ListBlockingIter(t.Context(), "", "", 0, nil) + gotItems = 0 + iter(func(item *Issue, err error) bool { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + return false + }) + if gotItems != 1 { + t.Errorf("client.Issues.ListBlockingIter call 4 got %v items; want 1 (an error)", gotItems) + } +} + func TestIssuesService_ListByOrgIter(t *testing.T) { t.Parallel() client, mux, _ := setup(t) From f57a237575d905643611cc17939f30baadb6492d Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 2 Apr 2026 15:57:04 +0100 Subject: [PATCH 03/15] chore: trigger CI From 76cfab327a7d53943e45a053209a7141a6344e90 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Fri, 3 Apr 2026 11:49:34 +0100 Subject: [PATCH 04/15] remove omitempty for required param --- github/issues_dependencies.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/issues_dependencies.go b/github/issues_dependencies.go index 18ee754b0ce..1f37601d299 100644 --- a/github/issues_dependencies.go +++ b/github/issues_dependencies.go @@ -12,7 +12,7 @@ import ( // IssueDependencyRequest represents a request to add a dependency to an issue. type IssueDependencyRequest struct { - IssueID *int64 `json:"issue_id,omitempty"` + IssueID *int64 `json:"issue_id"` } // ListBlockedBy lists the dependencies that block the specified issue. From 4550de5165726a1c8e9f757daf6ef30288421da0 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Fri, 3 Apr 2026 11:52:54 +0100 Subject: [PATCH 05/15] pass by value instead of reference as param is required --- github/issues_dependencies.go | 2 +- github/issues_dependencies_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/github/issues_dependencies.go b/github/issues_dependencies.go index 1f37601d299..22bad251ef4 100644 --- a/github/issues_dependencies.go +++ b/github/issues_dependencies.go @@ -46,7 +46,7 @@ func (s *IssuesService) ListBlockedBy(ctx context.Context, owner, repo string, n // GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#add-a-dependency-an-issue-is-blocked-by // //meta:operation POST /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by -func (s *IssuesService) AddBlockedBy(ctx context.Context, owner, repo string, number int, issueDepReq *IssueDependencyRequest) (*Issue, *Response, error) { +func (s *IssuesService) AddBlockedBy(ctx context.Context, owner, repo string, number int, issueDepReq IssueDependencyRequest) (*Issue, *Response, error) { u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by", owner, repo, number) req, err := s.client.NewRequest("POST", u, issueDepReq) if err != nil { diff --git a/github/issues_dependencies_test.go b/github/issues_dependencies_test.go index 0c44b3f72e4..4c8685669d4 100644 --- a/github/issues_dependencies_test.go +++ b/github/issues_dependencies_test.go @@ -64,10 +64,10 @@ func TestIssuesService_AddBlockedBy(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - input := &IssueDependencyRequest{IssueID: Ptr(int64(42))} + input := IssueDependencyRequest{IssueID: Ptr(int64(42))} mux.HandleFunc("/repos/o/r/issues/1/dependencies/blocked_by", func(w http.ResponseWriter, r *http.Request) { - var v *IssueDependencyRequest + var v IssueDependencyRequest assertNilError(t, json.NewDecoder(r.Body).Decode(&v)) testMethod(t, r, "POST") @@ -110,7 +110,7 @@ func TestIssuesService_AddBlockedBy_invalidOwner(t *testing.T) { client, _, _ := setup(t) ctx := t.Context() - _, _, err := client.Issues.AddBlockedBy(ctx, "%", "%", 1, nil) + _, _, err := client.Issues.AddBlockedBy(ctx, "%", "%", 1, IssueDependencyRequest{}) testURLParseError(t, err) } From 13f260ee6e520879eccc9f9e137dec6e0ba75201 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Wed, 8 Apr 2026 10:10:23 +0100 Subject: [PATCH 06/15] add missing fields to Issue struct --- github/github-accessors.go | 152 ++++++++++++++++++++++ github/github-accessors_test.go | 194 +++++++++++++++++++++++++++++ github/github-stringify_test.go | 68 +++++----- github/issues.go | 84 +++++++++---- github/issues_dependencies_test.go | 2 +- 5 files changed, 443 insertions(+), 57 deletions(-) diff --git a/github/github-accessors.go b/github/github-accessors.go index 573442acde1..1523d688e7d 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -17862,6 +17862,22 @@ func (i *Issue) GetID() int64 { return *i.ID } +// GetIssueDependenciesSummary returns the IssueDependenciesSummary field. +func (i *Issue) GetIssueDependenciesSummary() *IssueDependenciesSummary { + if i == nil { + return nil + } + return i.IssueDependenciesSummary +} + +// GetIssueFieldValues returns the IssueFieldValues slice if it's non-nil, nil otherwise. +func (i *Issue) GetIssueFieldValues() []*IssueFieldValue { + if i == nil || i.IssueFieldValues == nil { + return nil + } + return i.IssueFieldValues +} + // GetLabels returns the Labels slice if it's non-nil, nil otherwise. func (i *Issue) GetLabels() []*Label { if i == nil || i.Labels == nil { @@ -17918,6 +17934,22 @@ func (i *Issue) GetParentIssueURL() string { return *i.ParentIssueURL } +// GetPerformedViaGithubApp returns the PerformedViaGithubApp field. +func (i *Issue) GetPerformedViaGithubApp() *App { + if i == nil { + return nil + } + return i.PerformedViaGithubApp +} + +// GetPinnedComment returns the PinnedComment field. +func (i *Issue) GetPinnedComment() *IssueComment { + if i == nil { + return nil + } + return i.PinnedComment +} + // GetPullRequestLinks returns the PullRequestLinks field. func (i *Issue) GetPullRequestLinks() *PullRequestLinks { if i == nil { @@ -17966,6 +17998,14 @@ func (i *Issue) GetStateReason() string { return *i.StateReason } +// GetSubIssuesSummary returns the SubIssuesSummary field. +func (i *Issue) GetSubIssuesSummary() *SubIssuesSummary { + if i == nil { + return nil + } + return i.SubIssuesSummary +} + // GetTextMatches returns the TextMatches slice if it's non-nil, nil otherwise. func (i *Issue) GetTextMatches() []*TextMatch { if i == nil || i.TextMatches == nil { @@ -18166,6 +18206,38 @@ func (i *IssueCommentEvent) GetSender() *User { return i.Sender } +// GetBlockedBy returns the BlockedBy field if it's non-nil, zero value otherwise. +func (i *IssueDependenciesSummary) GetBlockedBy() int { + if i == nil || i.BlockedBy == nil { + return 0 + } + return *i.BlockedBy +} + +// GetBlocking returns the Blocking field if it's non-nil, zero value otherwise. +func (i *IssueDependenciesSummary) GetBlocking() int { + if i == nil || i.Blocking == nil { + return 0 + } + return *i.Blocking +} + +// GetTotalBlockedBy returns the TotalBlockedBy field if it's non-nil, zero value otherwise. +func (i *IssueDependenciesSummary) GetTotalBlockedBy() int { + if i == nil || i.TotalBlockedBy == nil { + return 0 + } + return *i.TotalBlockedBy +} + +// GetTotalBlocking returns the TotalBlocking field if it's non-nil, zero value otherwise. +func (i *IssueDependenciesSummary) GetTotalBlocking() int { + if i == nil || i.TotalBlocking == nil { + return 0 + } + return *i.TotalBlocking +} + // GetIssueID returns the IssueID field if it's non-nil, zero value otherwise. func (i *IssueDependencyRequest) GetIssueID() int64 { if i == nil || i.IssueID == nil { @@ -18334,6 +18406,62 @@ func (i *IssueEvent) GetURL() string { return *i.URL } +// GetColor returns the Color field if it's non-nil, zero value otherwise. +func (i *IssueFieldSelectOption) GetColor() string { + if i == nil || i.Color == nil { + return "" + } + return *i.Color +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (i *IssueFieldSelectOption) GetID() int64 { + if i == nil || i.ID == nil { + return 0 + } + return *i.ID +} + +// GetName returns the Name field if it's non-nil, zero value otherwise. +func (i *IssueFieldSelectOption) GetName() string { + if i == nil || i.Name == nil { + return "" + } + return *i.Name +} + +// GetDataType returns the DataType field if it's non-nil, zero value otherwise. +func (i *IssueFieldValue) GetDataType() string { + if i == nil || i.DataType == nil { + return "" + } + return *i.DataType +} + +// GetIssueFieldID returns the IssueFieldID field if it's non-nil, zero value otherwise. +func (i *IssueFieldValue) GetIssueFieldID() int64 { + if i == nil || i.IssueFieldID == nil { + return 0 + } + return *i.IssueFieldID +} + +// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise. +func (i *IssueFieldValue) GetNodeID() string { + if i == nil || i.NodeID == nil { + return "" + } + return *i.NodeID +} + +// GetSingleSelectOption returns the SingleSelectOption field. +func (i *IssueFieldValue) GetSingleSelectOption() *IssueFieldSelectOption { + if i == nil { + return nil + } + return i.SingleSelectOption +} + // GetAssignee returns the Assignee field if it's non-nil, zero value otherwise. func (i *IssueImport) GetAssignee() string { if i == nil || i.Assignee == nil { @@ -37782,6 +37910,30 @@ func (s *SubIssueRequest) GetSubIssueID() int64 { return s.SubIssueID } +// GetCompleted returns the Completed field if it's non-nil, zero value otherwise. +func (s *SubIssuesSummary) GetCompleted() int { + if s == nil || s.Completed == nil { + return 0 + } + return *s.Completed +} + +// GetPercentCompleted returns the PercentCompleted field if it's non-nil, zero value otherwise. +func (s *SubIssuesSummary) GetPercentCompleted() int { + if s == nil || s.PercentCompleted == nil { + return 0 + } + return *s.PercentCompleted +} + +// GetTotal returns the Total field if it's non-nil, zero value otherwise. +func (s *SubIssuesSummary) GetTotal() int { + if s == nil || s.Total == nil { + return 0 + } + return *s.Total +} + // GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. func (s *Subscription) GetCreatedAt() Timestamp { if s == nil || s.CreatedAt == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index f1052ca039e..1cb07418fc8 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -22609,6 +22609,25 @@ func TestIssue_GetID(tt *testing.T) { i.GetID() } +func TestIssue_GetIssueDependenciesSummary(tt *testing.T) { + tt.Parallel() + i := &Issue{} + i.GetIssueDependenciesSummary() + i = nil + i.GetIssueDependenciesSummary() +} + +func TestIssue_GetIssueFieldValues(tt *testing.T) { + tt.Parallel() + zeroValue := []*IssueFieldValue{} + i := &Issue{IssueFieldValues: zeroValue} + i.GetIssueFieldValues() + i = &Issue{} + i.GetIssueFieldValues() + i = nil + i.GetIssueFieldValues() +} + func TestIssue_GetLabels(tt *testing.T) { tt.Parallel() zeroValue := []*Label{} @@ -22683,6 +22702,22 @@ func TestIssue_GetParentIssueURL(tt *testing.T) { i.GetParentIssueURL() } +func TestIssue_GetPerformedViaGithubApp(tt *testing.T) { + tt.Parallel() + i := &Issue{} + i.GetPerformedViaGithubApp() + i = nil + i.GetPerformedViaGithubApp() +} + +func TestIssue_GetPinnedComment(tt *testing.T) { + tt.Parallel() + i := &Issue{} + i.GetPinnedComment() + i = nil + i.GetPinnedComment() +} + func TestIssue_GetPullRequestLinks(tt *testing.T) { tt.Parallel() i := &Issue{} @@ -22740,6 +22775,14 @@ func TestIssue_GetStateReason(tt *testing.T) { i.GetStateReason() } +func TestIssue_GetSubIssuesSummary(tt *testing.T) { + tt.Parallel() + i := &Issue{} + i.GetSubIssuesSummary() + i = nil + i.GetSubIssuesSummary() +} + func TestIssue_GetTextMatches(tt *testing.T) { tt.Parallel() zeroValue := []*TextMatch{} @@ -22982,6 +23025,50 @@ func TestIssueCommentEvent_GetSender(tt *testing.T) { i.GetSender() } +func TestIssueDependenciesSummary_GetBlockedBy(tt *testing.T) { + tt.Parallel() + var zeroValue int + i := &IssueDependenciesSummary{BlockedBy: &zeroValue} + i.GetBlockedBy() + i = &IssueDependenciesSummary{} + i.GetBlockedBy() + i = nil + i.GetBlockedBy() +} + +func TestIssueDependenciesSummary_GetBlocking(tt *testing.T) { + tt.Parallel() + var zeroValue int + i := &IssueDependenciesSummary{Blocking: &zeroValue} + i.GetBlocking() + i = &IssueDependenciesSummary{} + i.GetBlocking() + i = nil + i.GetBlocking() +} + +func TestIssueDependenciesSummary_GetTotalBlockedBy(tt *testing.T) { + tt.Parallel() + var zeroValue int + i := &IssueDependenciesSummary{TotalBlockedBy: &zeroValue} + i.GetTotalBlockedBy() + i = &IssueDependenciesSummary{} + i.GetTotalBlockedBy() + i = nil + i.GetTotalBlockedBy() +} + +func TestIssueDependenciesSummary_GetTotalBlocking(tt *testing.T) { + tt.Parallel() + var zeroValue int + i := &IssueDependenciesSummary{TotalBlocking: &zeroValue} + i.GetTotalBlocking() + i = &IssueDependenciesSummary{} + i.GetTotalBlocking() + i = nil + i.GetTotalBlocking() +} + func TestIssueDependencyRequest_GetIssueID(tt *testing.T) { tt.Parallel() var zeroValue int64 @@ -23171,6 +23258,80 @@ func TestIssueEvent_GetURL(tt *testing.T) { i.GetURL() } +func TestIssueFieldSelectOption_GetColor(tt *testing.T) { + tt.Parallel() + var zeroValue string + i := &IssueFieldSelectOption{Color: &zeroValue} + i.GetColor() + i = &IssueFieldSelectOption{} + i.GetColor() + i = nil + i.GetColor() +} + +func TestIssueFieldSelectOption_GetID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + i := &IssueFieldSelectOption{ID: &zeroValue} + i.GetID() + i = &IssueFieldSelectOption{} + i.GetID() + i = nil + i.GetID() +} + +func TestIssueFieldSelectOption_GetName(tt *testing.T) { + tt.Parallel() + var zeroValue string + i := &IssueFieldSelectOption{Name: &zeroValue} + i.GetName() + i = &IssueFieldSelectOption{} + i.GetName() + i = nil + i.GetName() +} + +func TestIssueFieldValue_GetDataType(tt *testing.T) { + tt.Parallel() + var zeroValue string + i := &IssueFieldValue{DataType: &zeroValue} + i.GetDataType() + i = &IssueFieldValue{} + i.GetDataType() + i = nil + i.GetDataType() +} + +func TestIssueFieldValue_GetIssueFieldID(tt *testing.T) { + tt.Parallel() + var zeroValue int64 + i := &IssueFieldValue{IssueFieldID: &zeroValue} + i.GetIssueFieldID() + i = &IssueFieldValue{} + i.GetIssueFieldID() + i = nil + i.GetIssueFieldID() +} + +func TestIssueFieldValue_GetNodeID(tt *testing.T) { + tt.Parallel() + var zeroValue string + i := &IssueFieldValue{NodeID: &zeroValue} + i.GetNodeID() + i = &IssueFieldValue{} + i.GetNodeID() + i = nil + i.GetNodeID() +} + +func TestIssueFieldValue_GetSingleSelectOption(tt *testing.T) { + tt.Parallel() + i := &IssueFieldValue{} + i.GetSingleSelectOption() + i = nil + i.GetSingleSelectOption() +} + func TestIssueImport_GetAssignee(tt *testing.T) { tt.Parallel() var zeroValue string @@ -47431,6 +47592,39 @@ func TestSubIssueRequest_GetSubIssueID(tt *testing.T) { s.GetSubIssueID() } +func TestSubIssuesSummary_GetCompleted(tt *testing.T) { + tt.Parallel() + var zeroValue int + s := &SubIssuesSummary{Completed: &zeroValue} + s.GetCompleted() + s = &SubIssuesSummary{} + s.GetCompleted() + s = nil + s.GetCompleted() +} + +func TestSubIssuesSummary_GetPercentCompleted(tt *testing.T) { + tt.Parallel() + var zeroValue int + s := &SubIssuesSummary{PercentCompleted: &zeroValue} + s.GetPercentCompleted() + s = &SubIssuesSummary{} + s.GetPercentCompleted() + s = nil + s.GetPercentCompleted() +} + +func TestSubIssuesSummary_GetTotal(tt *testing.T) { + tt.Parallel() + var zeroValue int + s := &SubIssuesSummary{Total: &zeroValue} + s.GetTotal() + s = &SubIssuesSummary{} + s.GetTotal() + s = nil + s.GetTotal() +} + func TestSubscription_GetCreatedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go index 98cdbbf9d0c..abd4c88fd5f 100644 --- a/github/github-stringify_test.go +++ b/github/github-stringify_test.go @@ -928,38 +928,42 @@ func TestInvitation_String(t *testing.T) { func TestIssue_String(t *testing.T) { t.Parallel() v := Issue{ - ID: Ptr(int64(0)), - Number: Ptr(0), - State: Ptr(""), - StateReason: Ptr(""), - Locked: Ptr(false), - Title: Ptr(""), - Body: Ptr(""), - AuthorAssociation: Ptr(""), - User: &User{}, - Assignee: &User{}, - Comments: Ptr(0), - ClosedAt: &Timestamp{}, - CreatedAt: &Timestamp{}, - UpdatedAt: &Timestamp{}, - ClosedBy: &User{}, - URL: Ptr(""), - HTMLURL: Ptr(""), - CommentsURL: Ptr(""), - EventsURL: Ptr(""), - LabelsURL: Ptr(""), - RepositoryURL: Ptr(""), - ParentIssueURL: Ptr(""), - Milestone: &Milestone{}, - PullRequestLinks: &PullRequestLinks{}, - Repository: &Repository{}, - Reactions: &Reactions{}, - NodeID: Ptr(""), - Draft: Ptr(false), - Type: &IssueType{}, - ActiveLockReason: Ptr(""), - } - want := `github.Issue{ID:0, Number:0, State:"", StateReason:"", Locked:false, Title:"", Body:"", AuthorAssociation:"", User:github.User{}, Assignee:github.User{}, Comments:0, ClosedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, ClosedBy:github.User{}, URL:"", HTMLURL:"", CommentsURL:"", EventsURL:"", LabelsURL:"", RepositoryURL:"", ParentIssueURL:"", Milestone:github.Milestone{}, PullRequestLinks:github.PullRequestLinks{}, Repository:github.Repository{}, Reactions:github.Reactions{}, NodeID:"", Draft:false, Type:github.IssueType{}, ActiveLockReason:""}` + ID: Ptr(int64(0)), + Number: Ptr(0), + State: Ptr(""), + StateReason: Ptr(""), + Locked: Ptr(false), + Title: Ptr(""), + Body: Ptr(""), + AuthorAssociation: Ptr(""), + User: &User{}, + Assignee: &User{}, + Comments: Ptr(0), + ClosedAt: &Timestamp{}, + CreatedAt: &Timestamp{}, + UpdatedAt: &Timestamp{}, + ClosedBy: &User{}, + URL: Ptr(""), + HTMLURL: Ptr(""), + CommentsURL: Ptr(""), + EventsURL: Ptr(""), + LabelsURL: Ptr(""), + RepositoryURL: Ptr(""), + ParentIssueURL: Ptr(""), + Milestone: &Milestone{}, + PullRequestLinks: &PullRequestLinks{}, + Repository: &Repository{}, + Reactions: &Reactions{}, + NodeID: Ptr(""), + Draft: Ptr(false), + Type: &IssueType{}, + PinnedComment: &IssueComment{}, + PerformedViaGithubApp: &App{}, + IssueDependenciesSummary: &IssueDependenciesSummary{}, + SubIssuesSummary: &SubIssuesSummary{}, + ActiveLockReason: Ptr(""), + } + want := `github.Issue{ID:0, Number:0, State:"", StateReason:"", Locked:false, Title:"", Body:"", AuthorAssociation:"", User:github.User{}, Assignee:github.User{}, Comments:0, ClosedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, ClosedBy:github.User{}, URL:"", HTMLURL:"", CommentsURL:"", EventsURL:"", LabelsURL:"", RepositoryURL:"", ParentIssueURL:"", Milestone:github.Milestone{}, PullRequestLinks:github.PullRequestLinks{}, Repository:github.Repository{}, Reactions:github.Reactions{}, NodeID:"", Draft:false, Type:github.IssueType{}, PinnedComment:github.IssueComment{}, PerformedViaGithubApp:github.App{}, IssueDependenciesSummary:github.IssueDependenciesSummary{}, SubIssuesSummary:github.SubIssuesSummary{}, ActiveLockReason:""}` if got := v.String(); got != want { t.Errorf("Issue.String = %v, want %v", got, want) } diff --git a/github/issues.go b/github/issues.go index f4c2c7b244e..9e2a4169a35 100644 --- a/github/issues.go +++ b/github/issues.go @@ -17,6 +17,37 @@ import ( // GitHub API docs: https://docs.github.com/rest/issues/ type IssuesService service +// IssueDependenciesSummary represents a summary of issue dependency counts. +type IssueDependenciesSummary struct { + BlockedBy *int `json:"blocked_by,omitempty"` + Blocking *int `json:"blocking,omitempty"` + TotalBlockedBy *int `json:"total_blocked_by,omitempty"` + TotalBlocking *int `json:"total_blocking,omitempty"` +} + +// SubIssuesSummary represents a summary of sub-issue progress. +type SubIssuesSummary struct { + Total *int `json:"total,omitempty"` + Completed *int `json:"completed,omitempty"` + PercentCompleted *int `json:"percent_completed,omitempty"` +} + +// IssueFieldSelectOption represents a selected option for a single_select issue field. +type IssueFieldSelectOption struct { + ID *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Color *string `json:"color,omitempty"` +} + +// IssueFieldValue represents a value assigned to an issue field. +type IssueFieldValue struct { + IssueFieldID *int64 `json:"issue_field_id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + DataType *string `json:"data_type,omitempty"` + Value interface{} `json:"value,omitempty"` + SingleSelectOption *IssueFieldSelectOption `json:"single_select_option,omitempty"` +} + // Issue represents a GitHub issue on a repository. // // Note: As far as the GitHub API is concerned, every pull request is an issue, @@ -39,30 +70,35 @@ type Issue struct { // Deprecated: GitHub will remove this field from Events API payloads on October 7, 2025. // Use the Issues REST API endpoint to retrieve this information. // See: https://docs.github.com/rest/issues/issues#get-an-issue - AuthorAssociation *string `json:"author_association,omitempty"` - User *User `json:"user,omitempty"` - Labels []*Label `json:"labels,omitempty"` - Assignee *User `json:"assignee,omitempty"` - Comments *int `json:"comments,omitempty"` - ClosedAt *Timestamp `json:"closed_at,omitempty"` - CreatedAt *Timestamp `json:"created_at,omitempty"` - UpdatedAt *Timestamp `json:"updated_at,omitempty"` - ClosedBy *User `json:"closed_by,omitempty"` - URL *string `json:"url,omitempty"` - HTMLURL *string `json:"html_url,omitempty"` - CommentsURL *string `json:"comments_url,omitempty"` - EventsURL *string `json:"events_url,omitempty"` - LabelsURL *string `json:"labels_url,omitempty"` - RepositoryURL *string `json:"repository_url,omitempty"` - ParentIssueURL *string `json:"parent_issue_url,omitempty"` - Milestone *Milestone `json:"milestone,omitempty"` - PullRequestLinks *PullRequestLinks `json:"pull_request,omitempty"` - Repository *Repository `json:"repository,omitempty"` - Reactions *Reactions `json:"reactions,omitempty"` - Assignees []*User `json:"assignees,omitempty"` - NodeID *string `json:"node_id,omitempty"` - Draft *bool `json:"draft,omitempty"` - Type *IssueType `json:"type,omitempty"` + AuthorAssociation *string `json:"author_association,omitempty"` + User *User `json:"user,omitempty"` + Labels []*Label `json:"labels,omitempty"` + Assignee *User `json:"assignee,omitempty"` + Comments *int `json:"comments,omitempty"` + ClosedAt *Timestamp `json:"closed_at,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + ClosedBy *User `json:"closed_by,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + CommentsURL *string `json:"comments_url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + LabelsURL *string `json:"labels_url,omitempty"` + RepositoryURL *string `json:"repository_url,omitempty"` + ParentIssueURL *string `json:"parent_issue_url,omitempty"` + Milestone *Milestone `json:"milestone,omitempty"` + PullRequestLinks *PullRequestLinks `json:"pull_request,omitempty"` + Repository *Repository `json:"repository,omitempty"` + Reactions *Reactions `json:"reactions,omitempty"` + Assignees []*User `json:"assignees,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Draft *bool `json:"draft,omitempty"` + Type *IssueType `json:"type,omitempty"` + PinnedComment *IssueComment `json:"pinned_comment,omitempty"` + PerformedViaGithubApp *App `json:"performed_via_github_app,omitempty"` + IssueDependenciesSummary *IssueDependenciesSummary `json:"issue_dependencies_summary,omitempty"` + SubIssuesSummary *SubIssuesSummary `json:"sub_issues_summary,omitempty"` + IssueFieldValues []*IssueFieldValue `json:"issue_field_values,omitempty"` // TextMatches is only populated from search results that request text matches // See: search.go and https://docs.github.com/rest/search/#text-match-metadata diff --git a/github/issues_dependencies_test.go b/github/issues_dependencies_test.go index 4c8685669d4..89c433633ad 100644 --- a/github/issues_dependencies_test.go +++ b/github/issues_dependencies_test.go @@ -206,7 +206,7 @@ func TestIssuesService_ListBlocking_invalidOwner(t *testing.T) { func TestIssueDependencyRequest_Marshal(t *testing.T) { t.Parallel() - testJSONMarshal(t, &IssueDependencyRequest{}, "{}") + testJSONMarshal(t, &IssueDependencyRequest{}, `{"issue_id":null}`) u := &IssueDependencyRequest{ IssueID: Ptr(int64(1)), From 6fd4bfdb97656876830a9f39d5223093363af763 Mon Sep 17 00:00:00 2001 From: Tommaso Moro <37270480+tommaso-moro@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:11:35 +0100 Subject: [PATCH 07/15] use any instead of interface{} Co-authored-by: Oleksandr Redko --- github/issues.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/issues.go b/github/issues.go index 9e2a4169a35..035055ae4f1 100644 --- a/github/issues.go +++ b/github/issues.go @@ -44,7 +44,7 @@ type IssueFieldValue struct { IssueFieldID *int64 `json:"issue_field_id,omitempty"` NodeID *string `json:"node_id,omitempty"` DataType *string `json:"data_type,omitempty"` - Value interface{} `json:"value,omitempty"` + Value any `json:"value,omitempty"` SingleSelectOption *IssueFieldSelectOption `json:"single_select_option,omitempty"` } From 171debacb3a48ac10d8968f5ca3f5cda9d71b09e Mon Sep 17 00:00:00 2001 From: Tommaso Moro <37270480+tommaso-moro@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:26:43 +0100 Subject: [PATCH 08/15] Use int64 instead of *int64 for IssueID Co-authored-by: Oleksandr Redko --- github/issues_dependencies.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/issues_dependencies.go b/github/issues_dependencies.go index 22bad251ef4..9e3f03c279a 100644 --- a/github/issues_dependencies.go +++ b/github/issues_dependencies.go @@ -12,7 +12,7 @@ import ( // IssueDependencyRequest represents a request to add a dependency to an issue. type IssueDependencyRequest struct { - IssueID *int64 `json:"issue_id"` + IssueID int64 `json:"issue_id"` } // ListBlockedBy lists the dependencies that block the specified issue. From c5e05bfd7627156bbe8fb5d29ff34ce34b8f9a55 Mon Sep 17 00:00:00 2001 From: Tommaso Moro <37270480+tommaso-moro@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:32:43 +0100 Subject: [PATCH 09/15] Update number param to issueNumber for consistency, in github/issues_dependencies.go Co-authored-by: Oleksandr Redko --- github/issues_dependencies.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/issues_dependencies.go b/github/issues_dependencies.go index 9e3f03c279a..467295548fe 100644 --- a/github/issues_dependencies.go +++ b/github/issues_dependencies.go @@ -20,7 +20,7 @@ type IssueDependencyRequest struct { // GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#list-dependencies-an-issue-is-blocked-by // //meta:operation GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by -func (s *IssuesService) ListBlockedBy(ctx context.Context, owner, repo string, number int, opts *ListOptions) ([]*Issue, *Response, error) { +func (s *IssuesService) ListBlockedBy(ctx context.Context, owner, repo string, issueNumber int, opts *ListOptions) ([]*Issue, *Response, error) { u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by", owner, repo, number) u, err := addOptions(u, opts) if err != nil { From 1d84d92bb85c8553599910f6bbb60cd500475f91 Mon Sep 17 00:00:00 2001 From: Tommaso Moro <37270480+tommaso-moro@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:33:03 +0100 Subject: [PATCH 10/15] Update number param to issueNumber in github/issues_dependencies.go Co-authored-by: Oleksandr Redko --- github/issues_dependencies.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/issues_dependencies.go b/github/issues_dependencies.go index 467295548fe..4d569f63aa0 100644 --- a/github/issues_dependencies.go +++ b/github/issues_dependencies.go @@ -46,7 +46,7 @@ func (s *IssuesService) ListBlockedBy(ctx context.Context, owner, repo string, i // GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#add-a-dependency-an-issue-is-blocked-by // //meta:operation POST /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by -func (s *IssuesService) AddBlockedBy(ctx context.Context, owner, repo string, number int, issueDepReq IssueDependencyRequest) (*Issue, *Response, error) { +func (s *IssuesService) AddBlockedBy(ctx context.Context, owner, repo string, issueNumber int, issueDepReq IssueDependencyRequest) (*Issue, *Response, error) { u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by", owner, repo, number) req, err := s.client.NewRequest("POST", u, issueDepReq) if err != nil { From a3c698372330a5beef74bdb53db12c6fe7f9e795 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 9 Apr 2026 10:34:47 +0100 Subject: [PATCH 11/15] update number param to issueNumber --- github/issues_dependencies_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/github/issues_dependencies_test.go b/github/issues_dependencies_test.go index 89c433633ad..6effe5dd01b 100644 --- a/github/issues_dependencies_test.go +++ b/github/issues_dependencies_test.go @@ -64,7 +64,7 @@ func TestIssuesService_AddBlockedBy(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - input := IssueDependencyRequest{IssueID: Ptr(int64(42))} + input := IssueDependencyRequest{IssueID: int64(42)} mux.HandleFunc("/repos/o/r/issues/1/dependencies/blocked_by", func(w http.ResponseWriter, r *http.Request) { var v IssueDependencyRequest @@ -209,7 +209,7 @@ func TestIssueDependencyRequest_Marshal(t *testing.T) { testJSONMarshal(t, &IssueDependencyRequest{}, `{"issue_id":null}`) u := &IssueDependencyRequest{ - IssueID: Ptr(int64(1)), + IssueID: int64(1), } want := `{ From 45fb09c0687468342b2194719187e0272922b2b2 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 9 Apr 2026 10:34:59 +0100 Subject: [PATCH 12/15] fix tests now that IssueID is of type int64 --- github/issues_dependencies.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/github/issues_dependencies.go b/github/issues_dependencies.go index 4d569f63aa0..4e7ac3e4819 100644 --- a/github/issues_dependencies.go +++ b/github/issues_dependencies.go @@ -21,7 +21,7 @@ type IssueDependencyRequest struct { // //meta:operation GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by func (s *IssuesService) ListBlockedBy(ctx context.Context, owner, repo string, issueNumber int, opts *ListOptions) ([]*Issue, *Response, error) { - u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by", owner, repo, number) + u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by", owner, repo, issueNumber) u, err := addOptions(u, opts) if err != nil { return nil, nil, err @@ -47,7 +47,7 @@ func (s *IssuesService) ListBlockedBy(ctx context.Context, owner, repo string, i // //meta:operation POST /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by func (s *IssuesService) AddBlockedBy(ctx context.Context, owner, repo string, issueNumber int, issueDepReq IssueDependencyRequest) (*Issue, *Response, error) { - u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by", owner, repo, number) + u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by", owner, repo, issueNumber) req, err := s.client.NewRequest("POST", u, issueDepReq) if err != nil { return nil, nil, err @@ -67,8 +67,8 @@ func (s *IssuesService) AddBlockedBy(ctx context.Context, owner, repo string, is // GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#remove-dependency-an-issue-is-blocked-by // //meta:operation DELETE /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by/{issue_id} -func (s *IssuesService) RemoveBlockedBy(ctx context.Context, owner, repo string, number int, issueID int64) (*Issue, *Response, error) { - u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by/%v", owner, repo, number, issueID) +func (s *IssuesService) RemoveBlockedBy(ctx context.Context, owner, repo string, issueNumber int, issueID int64) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by/%v", owner, repo, issueNumber, issueID) req, err := s.client.NewRequest("DELETE", u, nil) if err != nil { return nil, nil, err From 1e86822a4c7eddfe405231f3146b465d6d2d2df2 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 9 Apr 2026 10:36:05 +0100 Subject: [PATCH 13/15] update number param to issueNumber --- github/issues_dependencies.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/github/issues_dependencies.go b/github/issues_dependencies.go index 4e7ac3e4819..8aaa7e66515 100644 --- a/github/issues_dependencies.go +++ b/github/issues_dependencies.go @@ -88,8 +88,8 @@ func (s *IssuesService) RemoveBlockedBy(ctx context.Context, owner, repo string, // GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#list-dependencies-an-issue-is-blocking // //meta:operation GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocking -func (s *IssuesService) ListBlocking(ctx context.Context, owner, repo string, number int, opts *ListOptions) ([]*Issue, *Response, error) { - u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocking", owner, repo, number) +func (s *IssuesService) ListBlocking(ctx context.Context, owner, repo string, issueNumber int, opts *ListOptions) ([]*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocking", owner, repo, issueNumber) u, err := addOptions(u, opts) if err != nil { return nil, nil, err From 0000b64f855bbce6ab88719443b0384f8cf3566e Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 9 Apr 2026 10:53:57 +0100 Subject: [PATCH 14/15] Use int64 for issueNumber parameter Per review feedback: both issue_id and issue_number are integer in the OpenAPI spec, so issueNumber should be int64 for consistency. --- github/github-accessors.go | 14 +++++++++++--- github/github-accessors_test.go | 13 +++++++++---- github/github-iterators.go | 8 ++++---- github/issues_dependencies.go | 8 ++++---- github/issues_dependencies_test.go | 2 +- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/github/github-accessors.go b/github/github-accessors.go index 1523d688e7d..bb0fe129c7e 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -18238,12 +18238,12 @@ func (i *IssueDependenciesSummary) GetTotalBlocking() int { return *i.TotalBlocking } -// GetIssueID returns the IssueID field if it's non-nil, zero value otherwise. +// GetIssueID returns the IssueID field. func (i *IssueDependencyRequest) GetIssueID() int64 { - if i == nil || i.IssueID == nil { + if i == nil { return 0 } - return *i.IssueID + return i.IssueID } // GetAction returns the Action field. @@ -18462,6 +18462,14 @@ func (i *IssueFieldValue) GetSingleSelectOption() *IssueFieldSelectOption { return i.SingleSelectOption } +// GetValue returns the Value field. +func (i *IssueFieldValue) GetValue() any { + if i == nil { + return nil + } + return i.Value +} + // GetAssignee returns the Assignee field if it's non-nil, zero value otherwise. func (i *IssueImport) GetAssignee() string { if i == nil || i.Assignee == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 1cb07418fc8..968d2b8ed39 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -23071,10 +23071,7 @@ func TestIssueDependenciesSummary_GetTotalBlocking(tt *testing.T) { func TestIssueDependencyRequest_GetIssueID(tt *testing.T) { tt.Parallel() - var zeroValue int64 - i := &IssueDependencyRequest{IssueID: &zeroValue} - i.GetIssueID() - i = &IssueDependencyRequest{} + i := &IssueDependencyRequest{} i.GetIssueID() i = nil i.GetIssueID() @@ -23332,6 +23329,14 @@ func TestIssueFieldValue_GetSingleSelectOption(tt *testing.T) { i.GetSingleSelectOption() } +func TestIssueFieldValue_GetValue(tt *testing.T) { + tt.Parallel() + i := &IssueFieldValue{} + i.GetValue() + i = nil + i.GetValue() +} + func TestIssueImport_GetAssignee(tt *testing.T) { tt.Parallel() var zeroValue string diff --git a/github/github-iterators.go b/github/github-iterators.go index a1c5388e07e..c6469aca716 100644 --- a/github/github-iterators.go +++ b/github/github-iterators.go @@ -3405,7 +3405,7 @@ func (s *IssuesService) ListAssigneesIter(ctx context.Context, owner string, rep } // ListBlockedByIter returns an iterator that paginates through all results of ListBlockedBy. -func (s *IssuesService) ListBlockedByIter(ctx context.Context, owner string, repo string, number int, opts *ListOptions) iter.Seq2[*Issue, error] { +func (s *IssuesService) ListBlockedByIter(ctx context.Context, owner string, repo string, issueNumber int64, opts *ListOptions) iter.Seq2[*Issue, error] { return func(yield func(*Issue, error) bool) { // Create a copy of opts to avoid mutating the caller's struct if opts == nil { @@ -3415,7 +3415,7 @@ func (s *IssuesService) ListBlockedByIter(ctx context.Context, owner string, rep } for { - results, resp, err := s.ListBlockedBy(ctx, owner, repo, number, opts) + results, resp, err := s.ListBlockedBy(ctx, owner, repo, issueNumber, opts) if err != nil { yield(nil, err) return @@ -3436,7 +3436,7 @@ func (s *IssuesService) ListBlockedByIter(ctx context.Context, owner string, rep } // ListBlockingIter returns an iterator that paginates through all results of ListBlocking. -func (s *IssuesService) ListBlockingIter(ctx context.Context, owner string, repo string, number int, opts *ListOptions) iter.Seq2[*Issue, error] { +func (s *IssuesService) ListBlockingIter(ctx context.Context, owner string, repo string, issueNumber int64, opts *ListOptions) iter.Seq2[*Issue, error] { return func(yield func(*Issue, error) bool) { // Create a copy of opts to avoid mutating the caller's struct if opts == nil { @@ -3446,7 +3446,7 @@ func (s *IssuesService) ListBlockingIter(ctx context.Context, owner string, repo } for { - results, resp, err := s.ListBlocking(ctx, owner, repo, number, opts) + results, resp, err := s.ListBlocking(ctx, owner, repo, issueNumber, opts) if err != nil { yield(nil, err) return diff --git a/github/issues_dependencies.go b/github/issues_dependencies.go index 8aaa7e66515..e2d067f400b 100644 --- a/github/issues_dependencies.go +++ b/github/issues_dependencies.go @@ -20,7 +20,7 @@ type IssueDependencyRequest struct { // GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#list-dependencies-an-issue-is-blocked-by // //meta:operation GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by -func (s *IssuesService) ListBlockedBy(ctx context.Context, owner, repo string, issueNumber int, opts *ListOptions) ([]*Issue, *Response, error) { +func (s *IssuesService) ListBlockedBy(ctx context.Context, owner, repo string, issueNumber int64, opts *ListOptions) ([]*Issue, *Response, error) { u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by", owner, repo, issueNumber) u, err := addOptions(u, opts) if err != nil { @@ -46,7 +46,7 @@ func (s *IssuesService) ListBlockedBy(ctx context.Context, owner, repo string, i // GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#add-a-dependency-an-issue-is-blocked-by // //meta:operation POST /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by -func (s *IssuesService) AddBlockedBy(ctx context.Context, owner, repo string, issueNumber int, issueDepReq IssueDependencyRequest) (*Issue, *Response, error) { +func (s *IssuesService) AddBlockedBy(ctx context.Context, owner, repo string, issueNumber int64, issueDepReq IssueDependencyRequest) (*Issue, *Response, error) { u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by", owner, repo, issueNumber) req, err := s.client.NewRequest("POST", u, issueDepReq) if err != nil { @@ -67,7 +67,7 @@ func (s *IssuesService) AddBlockedBy(ctx context.Context, owner, repo string, is // GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#remove-dependency-an-issue-is-blocked-by // //meta:operation DELETE /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by/{issue_id} -func (s *IssuesService) RemoveBlockedBy(ctx context.Context, owner, repo string, issueNumber int, issueID int64) (*Issue, *Response, error) { +func (s *IssuesService) RemoveBlockedBy(ctx context.Context, owner, repo string, issueNumber int64, issueID int64) (*Issue, *Response, error) { u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocked_by/%v", owner, repo, issueNumber, issueID) req, err := s.client.NewRequest("DELETE", u, nil) if err != nil { @@ -88,7 +88,7 @@ func (s *IssuesService) RemoveBlockedBy(ctx context.Context, owner, repo string, // GitHub API docs: https://docs.github.com/rest/issues/issue-dependencies#list-dependencies-an-issue-is-blocking // //meta:operation GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocking -func (s *IssuesService) ListBlocking(ctx context.Context, owner, repo string, issueNumber int, opts *ListOptions) ([]*Issue, *Response, error) { +func (s *IssuesService) ListBlocking(ctx context.Context, owner, repo string, issueNumber int64, opts *ListOptions) ([]*Issue, *Response, error) { u := fmt.Sprintf("repos/%v/%v/issues/%v/dependencies/blocking", owner, repo, issueNumber) u, err := addOptions(u, opts) if err != nil { diff --git a/github/issues_dependencies_test.go b/github/issues_dependencies_test.go index 6effe5dd01b..f09c526f006 100644 --- a/github/issues_dependencies_test.go +++ b/github/issues_dependencies_test.go @@ -206,7 +206,7 @@ func TestIssuesService_ListBlocking_invalidOwner(t *testing.T) { func TestIssueDependencyRequest_Marshal(t *testing.T) { t.Parallel() - testJSONMarshal(t, &IssueDependencyRequest{}, `{"issue_id":null}`) + testJSONMarshal(t, &IssueDependencyRequest{}, `{"issue_id":0}`) u := &IssueDependencyRequest{ IssueID: int64(1), From d261d4f86980581bee67f3ca488fdbac6664a843 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Thu, 9 Apr 2026 11:00:41 +0100 Subject: [PATCH 15/15] clean up test --- github/issues_dependencies_test.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/github/issues_dependencies_test.go b/github/issues_dependencies_test.go index f09c526f006..210bc8ef52c 100644 --- a/github/issues_dependencies_test.go +++ b/github/issues_dependencies_test.go @@ -6,7 +6,6 @@ package github import ( - "encoding/json" "fmt" "net/http" "testing" @@ -67,13 +66,8 @@ func TestIssuesService_AddBlockedBy(t *testing.T) { input := IssueDependencyRequest{IssueID: int64(42)} mux.HandleFunc("/repos/o/r/issues/1/dependencies/blocked_by", func(w http.ResponseWriter, r *http.Request) { - var v IssueDependencyRequest - assertNilError(t, json.NewDecoder(r.Body).Decode(&v)) - testMethod(t, r, "POST") - if !cmp.Equal(v, input) { - t.Errorf("Request body = %+v, want %+v", v, input) - } + testBody(t, r, `{"issue_id":42}`+"\n") w.WriteHeader(http.StatusCreated) fmt.Fprint(w, `{"number":42,"title":"Dependency issue"}`)