From b09a77847d0578a67e2fd89a9af7c63e5c50be80 Mon Sep 17 00:00:00 2001 From: JamBalaya56562 Date: Mon, 26 Oct 2026 01:23:36 +0900 Subject: [PATCH 1/2] feat!: Add Copilot content exclusion set and enterprise endpoints --- github/copilot.go | 106 ++++++++++++++++++++++-- github/copilot_test.go | 142 +++++++++++++++++++++++++++++++- github/github-accessors.go | 32 +++++++ github/github-accessors_test.go | 44 ++++++++++ 4 files changed, 317 insertions(+), 7 deletions(-) diff --git a/github/copilot.go b/github/copilot.go index 0c8a8b7aa4e..7506cee22eb 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -712,17 +712,45 @@ func (s *CopilotService) DisableOrganizationCodingAgentRepository(ctx context.Co return s.client.Do(req, nil) } -// CopilotOrganizationContentExclusionDetails lists all Copilot content exclusion -// rules for an organization, keyed by repository full name. Each value is the -// list of file paths excluded from Copilot for that repository. -type CopilotOrganizationContentExclusionDetails map[string][]string +// CopilotContentExclusionDetails lists Copilot content exclusion path rules, +// keyed by repository identifier. Each value is the list of file paths excluded +// from Copilot for that repository. +type CopilotContentExclusionDetails map[string][]string + +// CopilotSetContentExclusionDetailsRequest represents the Copilot content exclusion +// rules to set, keyed by repository identifier. +type CopilotSetContentExclusionDetailsRequest map[string][]*CopilotContentExclusionRule + +// CopilotContentExclusionRule represents a single Copilot content exclusion rule. +// Exactly one of Path, IfAnyMatch, or IfNoneMatch should be set. +type CopilotContentExclusionRule struct { + Path *string `json:"-"` + IfAnyMatch []string `json:"ifAnyMatch,omitempty"` + IfNoneMatch []string `json:"ifNoneMatch,omitempty"` +} + +// MarshalJSON implements the json.Marshaler interface. +// A rule with Path set is encoded as a plain string. +func (r CopilotContentExclusionRule) MarshalJSON() ([]byte, error) { + if r.Path != nil { + return json.Marshal(*r.Path) + } + type alias CopilotContentExclusionRule + return json.Marshal(alias(r)) +} + +// CopilotSetContentExclusionResponse represents the response from setting +// Copilot content exclusion rules. +type CopilotSetContentExclusionResponse struct { + Message *string `json:"message,omitempty"` +} // GetOrganizationContentExclusionDetails gets the Copilot content exclusion rules for an organization. // // GitHub API docs: https://docs.github.com/rest/copilot/copilot-content-exclusion-management?apiVersion=2022-11-28#get-copilot-content-exclusion-rules-for-an-organization // //meta:operation GET /orgs/{org}/copilot/content_exclusion -func (s *CopilotService) GetOrganizationContentExclusionDetails(ctx context.Context, org string) (CopilotOrganizationContentExclusionDetails, *Response, error) { +func (s *CopilotService) GetOrganizationContentExclusionDetails(ctx context.Context, org string) (CopilotContentExclusionDetails, *Response, error) { u := fmt.Sprintf("orgs/%v/copilot/content_exclusion", org) req, err := s.client.NewRequest(ctx, "GET", u, nil) @@ -730,7 +758,7 @@ func (s *CopilotService) GetOrganizationContentExclusionDetails(ctx context.Cont return nil, nil, err } - details := CopilotOrganizationContentExclusionDetails{} + details := CopilotContentExclusionDetails{} resp, err := s.client.Do(req, &details) if err != nil { return nil, resp, err @@ -739,6 +767,72 @@ func (s *CopilotService) GetOrganizationContentExclusionDetails(ctx context.Cont return details, resp, nil } +// SetOrganizationContentExclusionDetails sets the Copilot content exclusion rules for an organization. +// +// GitHub API docs: https://docs.github.com/rest/copilot/copilot-content-exclusion-management?apiVersion=2022-11-28#set-copilot-content-exclusion-rules-for-an-organization +// +//meta:operation PUT /orgs/{org}/copilot/content_exclusion +func (s *CopilotService) SetOrganizationContentExclusionDetails(ctx context.Context, org string, body CopilotSetContentExclusionDetailsRequest) (*CopilotSetContentExclusionResponse, *Response, error) { + u := fmt.Sprintf("orgs/%v/copilot/content_exclusion", org) + + req, err := s.client.NewRequest(ctx, "PUT", u, body) + if err != nil { + return nil, nil, err + } + + var result *CopilotSetContentExclusionResponse + resp, err := s.client.Do(req, &result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// GetEnterpriseContentExclusionDetails gets the Copilot content exclusion rules for an enterprise. +// +// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/copilot/copilot-content-exclusion-management?apiVersion=2022-11-28#get-copilot-content-exclusion-rules-for-an-enterprise +// +//meta:operation GET /enterprises/{enterprise}/copilot/content_exclusion +func (s *CopilotService) GetEnterpriseContentExclusionDetails(ctx context.Context, enterprise string) (CopilotContentExclusionDetails, *Response, error) { + u := fmt.Sprintf("enterprises/%v/copilot/content_exclusion", enterprise) + + req, err := s.client.NewRequest(ctx, "GET", u, nil) + if err != nil { + return nil, nil, err + } + + details := CopilotContentExclusionDetails{} + resp, err := s.client.Do(req, &details) + if err != nil { + return nil, resp, err + } + + return details, resp, nil +} + +// SetEnterpriseContentExclusionDetails sets the Copilot content exclusion rules for an enterprise. +// +// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/copilot/copilot-content-exclusion-management?apiVersion=2022-11-28#set-copilot-content-exclusion-rules-for-an-enterprise +// +//meta:operation PUT /enterprises/{enterprise}/copilot/content_exclusion +func (s *CopilotService) SetEnterpriseContentExclusionDetails(ctx context.Context, enterprise string, body CopilotSetContentExclusionDetailsRequest) (*CopilotSetContentExclusionResponse, *Response, error) { + u := fmt.Sprintf("enterprises/%v/copilot/content_exclusion", enterprise) + + req, err := s.client.NewRequest(ctx, "PUT", u, body) + if err != nil { + return nil, nil, err + } + + var result *CopilotSetContentExclusionResponse + resp, err := s.client.Do(req, &result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + // AddCopilotTeams adds teams to the Copilot for Business subscription for an organization. // // GitHub API docs: https://docs.github.com/rest/copilot/copilot-user-management?apiVersion=2022-11-28#add-teams-to-the-copilot-subscription-for-an-organization diff --git a/github/copilot_test.go b/github/copilot_test.go index f90d01878ee..3c434d78a45 100644 --- a/github/copilot_test.go +++ b/github/copilot_test.go @@ -1246,7 +1246,7 @@ func TestCopilotService_GetOrganizationContentExclusionDetails(t *testing.T) { t.Errorf("Copilot.GetOrganizationContentExclusionDetails returned error: %v", err) } - want := CopilotOrganizationContentExclusionDetails{ + want := CopilotContentExclusionDetails{ "octo-repo": {"/src/some-dir/kernel.rs"}, "octo-repo-2": {"/docs/secret.md", "**/*.env"}, } @@ -1270,6 +1270,146 @@ func TestCopilotService_GetOrganizationContentExclusionDetails(t *testing.T) { }) } +func TestCopilotContentExclusionRule_Marshal(t *testing.T) { + t.Parallel() + + testJSONMarshalOnly(t, CopilotContentExclusionRule{Path: new("/src/some-dir/kernel.rs")}, `"/src/some-dir/kernel.rs"`) + testJSONMarshalOnly(t, CopilotContentExclusionRule{IfAnyMatch: []string{"*.env"}}, `{"ifAnyMatch":["*.env"]}`) + testJSONMarshalOnly(t, CopilotContentExclusionRule{IfNoneMatch: []string{"*.go", "*.md"}}, `{"ifNoneMatch":["*.go","*.md"]}`) +} + +func TestCopilotService_SetOrganizationContentExclusionDetails(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + body := CopilotSetContentExclusionDetailsRequest{ + "octo-repo": { + {Path: new("/src/some-dir/kernel.rs")}, + {IfAnyMatch: []string{"*.env"}}, + }, + } + + mux.HandleFunc("/orgs/o/copilot/content_exclusion", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testJSONBody(t, r, body) + fmt.Fprint(w, `{"message": "Content exclusion rules updated successfully."}`) + }) + + ctx := t.Context() + got, _, err := client.Copilot.SetOrganizationContentExclusionDetails(ctx, "o", body) + if err != nil { + t.Errorf("Copilot.SetOrganizationContentExclusionDetails returned error: %v", err) + } + + want := &CopilotSetContentExclusionResponse{ + Message: new("Content exclusion rules updated successfully."), + } + if !cmp.Equal(got, want) { + t.Errorf("Copilot.SetOrganizationContentExclusionDetails returned %+v, want %+v", got, want) + } + + const methodName = "SetOrganizationContentExclusionDetails" + + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Copilot.SetOrganizationContentExclusionDetails(ctx, "\n", body) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Copilot.SetOrganizationContentExclusionDetails(ctx, "o", body) + if got != nil { + t.Errorf("Copilot.SetOrganizationContentExclusionDetails returned %+v, want nil", got) + } + return resp, err + }) +} + +func TestCopilotService_GetEnterpriseContentExclusionDetails(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/copilot/content_exclusion", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{ + "git@github.com:*/copilot": ["/__tests__/**"], + "octo-org/octo-repo": ["/src/some-dir/kernel.rs"] + }`) + }) + + ctx := t.Context() + got, _, err := client.Copilot.GetEnterpriseContentExclusionDetails(ctx, "e") + if err != nil { + t.Errorf("Copilot.GetEnterpriseContentExclusionDetails returned error: %v", err) + } + + want := CopilotContentExclusionDetails{ + "git@github.com:*/copilot": {"/__tests__/**"}, + "octo-org/octo-repo": {"/src/some-dir/kernel.rs"}, + } + if !cmp.Equal(got, want) { + t.Errorf("Copilot.GetEnterpriseContentExclusionDetails returned %+v, want %+v", got, want) + } + + const methodName = "GetEnterpriseContentExclusionDetails" + + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Copilot.GetEnterpriseContentExclusionDetails(ctx, "\n") + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Copilot.GetEnterpriseContentExclusionDetails(ctx, "e") + if got != nil { + t.Errorf("Copilot.GetEnterpriseContentExclusionDetails returned %+v, want nil", got) + } + return resp, err + }) +} + +func TestCopilotService_SetEnterpriseContentExclusionDetails(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + body := CopilotSetContentExclusionDetailsRequest{ + "git@github.com:*/copilot": {{Path: new("/__tests__/**")}}, + "octo-org/octo-repo": {{IfNoneMatch: []string{"*.go"}}}, + } + + mux.HandleFunc("/enterprises/e/copilot/content_exclusion", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testJSONBody(t, r, body) + fmt.Fprint(w, `{"message": "Content exclusion rules updated successfully."}`) + }) + + ctx := t.Context() + got, _, err := client.Copilot.SetEnterpriseContentExclusionDetails(ctx, "e", body) + if err != nil { + t.Errorf("Copilot.SetEnterpriseContentExclusionDetails returned error: %v", err) + } + + want := &CopilotSetContentExclusionResponse{ + Message: new("Content exclusion rules updated successfully."), + } + if !cmp.Equal(got, want) { + t.Errorf("Copilot.SetEnterpriseContentExclusionDetails returned %+v, want %+v", got, want) + } + + const methodName = "SetEnterpriseContentExclusionDetails" + + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Copilot.SetEnterpriseContentExclusionDetails(ctx, "\n", body) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Copilot.SetEnterpriseContentExclusionDetails(ctx, "e", body) + if got != nil { + t.Errorf("Copilot.SetEnterpriseContentExclusionDetails returned %+v, want nil", got) + } + return resp, err + }) +} + func TestCopilotService_AddCopilotTeams(t *testing.T) { t.Parallel() client, mux, _ := setup(t) diff --git a/github/github-accessors.go b/github/github-accessors.go index 81bb6c4d3bd..d99ad2e5c99 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -9502,6 +9502,30 @@ func (c *CopilotCodingAgentPermissionsRequest) GetEnabledRepositories() string { return c.EnabledRepositories } +// GetIfAnyMatch returns the IfAnyMatch slice if it's non-nil, nil otherwise. +func (c *CopilotContentExclusionRule) GetIfAnyMatch() []string { + if c == nil || c.IfAnyMatch == nil { + return nil + } + return c.IfAnyMatch +} + +// GetIfNoneMatch returns the IfNoneMatch slice if it's non-nil, nil otherwise. +func (c *CopilotContentExclusionRule) GetIfNoneMatch() []string { + if c == nil || c.IfNoneMatch == nil { + return nil + } + return c.IfNoneMatch +} + +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (c *CopilotContentExclusionRule) GetPath() string { + if c == nil || c.Path == nil { + return "" + } + return *c.Path +} + // GetCodeAcceptanceActivityCount returns the CodeAcceptanceActivityCount field if it's non-nil, zero value otherwise. func (c *CopilotDailyMetrics) GetCodeAcceptanceActivityCount() int { if c == nil || c.CodeAcceptanceActivityCount == nil { @@ -11182,6 +11206,14 @@ func (c *CopilotSeatDetails) GetUpdatedAt() Timestamp { return *c.UpdatedAt } +// GetMessage returns the Message field if it's non-nil, zero value otherwise. +func (c *CopilotSetContentExclusionResponse) GetMessage() string { + if c == nil || c.Message == nil { + return "" + } + return *c.Message +} + // GetAPIURL returns the APIURL field. func (c *CopilotSpace) GetAPIURL() string { if c == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 7f06c475b8f..51b88ad3f3c 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -12065,6 +12065,39 @@ func TestCopilotCodingAgentPermissionsRequest_GetEnabledRepositories(tt *testing c.GetEnabledRepositories() } +func TestCopilotContentExclusionRule_GetIfAnyMatch(tt *testing.T) { + tt.Parallel() + zeroValue := []string{} + c := &CopilotContentExclusionRule{IfAnyMatch: zeroValue} + c.GetIfAnyMatch() + c = &CopilotContentExclusionRule{} + c.GetIfAnyMatch() + c = nil + c.GetIfAnyMatch() +} + +func TestCopilotContentExclusionRule_GetIfNoneMatch(tt *testing.T) { + tt.Parallel() + zeroValue := []string{} + c := &CopilotContentExclusionRule{IfNoneMatch: zeroValue} + c.GetIfNoneMatch() + c = &CopilotContentExclusionRule{} + c.GetIfNoneMatch() + c = nil + c.GetIfNoneMatch() +} + +func TestCopilotContentExclusionRule_GetPath(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CopilotContentExclusionRule{Path: &zeroValue} + c.GetPath() + c = &CopilotContentExclusionRule{} + c.GetPath() + c = nil + c.GetPath() +} + func TestCopilotDailyMetrics_GetCodeAcceptanceActivityCount(tt *testing.T) { tt.Parallel() var zeroValue int @@ -14072,6 +14105,17 @@ func TestCopilotSeatDetails_GetUpdatedAt(tt *testing.T) { c.GetUpdatedAt() } +func TestCopilotSetContentExclusionResponse_GetMessage(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CopilotSetContentExclusionResponse{Message: &zeroValue} + c.GetMessage() + c = &CopilotSetContentExclusionResponse{} + c.GetMessage() + c = nil + c.GetMessage() +} + func TestCopilotSpace_GetAPIURL(tt *testing.T) { tt.Parallel() c := &CopilotSpace{} From 93f28b82ccb944ddae6d0fb424f7b87d69cc9500 Mon Sep 17 00:00:00 2001 From: JamBalaya56562 Date: Mon, 26 Oct 2026 19:49:21 +0900 Subject: [PATCH 2/2] test: Convert `CopilotContentExclusionRule` marshal test to table-driven --- github/copilot_test.go | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/github/copilot_test.go b/github/copilot_test.go index 3c434d78a45..c859e0237d5 100644 --- a/github/copilot_test.go +++ b/github/copilot_test.go @@ -1273,9 +1273,34 @@ func TestCopilotService_GetOrganizationContentExclusionDetails(t *testing.T) { func TestCopilotContentExclusionRule_Marshal(t *testing.T) { t.Parallel() - testJSONMarshalOnly(t, CopilotContentExclusionRule{Path: new("/src/some-dir/kernel.rs")}, `"/src/some-dir/kernel.rs"`) - testJSONMarshalOnly(t, CopilotContentExclusionRule{IfAnyMatch: []string{"*.env"}}, `{"ifAnyMatch":["*.env"]}`) - testJSONMarshalOnly(t, CopilotContentExclusionRule{IfNoneMatch: []string{"*.go", "*.md"}}, `{"ifNoneMatch":["*.go","*.md"]}`) + tests := []struct { + name string + rule CopilotContentExclusionRule + want string + }{ + { + name: "path", + rule: CopilotContentExclusionRule{Path: new("/src/some-dir/kernel.rs")}, + want: `"/src/some-dir/kernel.rs"`, + }, + { + name: "ifAnyMatch", + rule: CopilotContentExclusionRule{IfAnyMatch: []string{"*.env"}}, + want: `{"ifAnyMatch":["*.env"]}`, + }, + { + name: "ifNoneMatch", + rule: CopilotContentExclusionRule{IfNoneMatch: []string{"*.go", "*.md"}}, + want: `{"ifNoneMatch":["*.go","*.md"]}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + testJSONMarshalOnly(t, tt.rule, tt.want) + }) + } } func TestCopilotService_SetOrganizationContentExclusionDetails(t *testing.T) {