diff --git a/github/github-accessors.go b/github/github-accessors.go index 3ec79c2ca33..62b5ff7f907 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -10100,6 +10100,22 @@ func (s *ServiceHook) GetName() string { return *s.Name } +// GetEnabled returns the Enabled field if it's non-nil, zero value otherwise. +func (s *SignaturesProtectedBranch) GetEnabled() bool { + if s == nil || s.Enabled == nil { + return false + } + return *s.Enabled +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (s *SignaturesProtectedBranch) GetURL() string { + if s == nil || s.URL == nil { + return "" + } + return *s.URL +} + // GetPayload returns the Payload field if it's non-nil, zero value otherwise. func (s *SignatureVerification) GetPayload() string { if s == nil || s.Payload == nil { diff --git a/github/github.go b/github/github.go index 320b8104b67..d00c5e0c2a4 100644 --- a/github/github.go +++ b/github/github.go @@ -122,6 +122,9 @@ const ( // https://developer.github.com/enterprise/2.13/v3/repos/pre_receive_hooks/ mediaTypePreReceiveHooksPreview = "application/vnd.github.eye-scream-preview" + + // https://developer.github.com/changes/2018-02-22-protected-branches-required-signatures/ + mediaTypeSignaturePreview = "application/vnd.github.zzzax-preview+json" ) // A Client manages communication with the GitHub API. diff --git a/github/repos.go b/github/repos.go index e783ccbea03..d09b5cc87e6 100644 --- a/github/repos.go +++ b/github/repos.go @@ -698,6 +698,13 @@ type DismissalRestrictionsRequest struct { Teams *[]string `json:"teams,omitempty"` } +// SignaturesProtectedBranch represents the protection status of an individual branch. +type SignaturesProtectedBranch struct { + URL *string `json:"url,omitempty"` + // Commits pushed to matching branches must have verified signatures. + Enabled *bool `json:"enabled,omitempty"` +} + // ListBranches lists branches for the specified repository. // // GitHub API docs: https://developer.github.com/v3/repos/#list-branches @@ -850,6 +857,67 @@ func (s *RepositoriesService) RemoveBranchProtection(ctx context.Context, owner, return s.client.Do(ctx, req, nil) } +// GetSignaturesProtectedBranch gets required signatures of protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#get-required-signatures-of-protected-branch +func (s *RepositoriesService) GetSignaturesProtectedBranch(ctx context.Context, owner, repo, branch string) (*SignaturesProtectedBranch, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_signatures", owner, repo, branch) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeSignaturePreview) + + p := new(SignaturesProtectedBranch) + resp, err := s.client.Do(ctx, req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, nil +} + +// RequireSignaturesOnProtectedBranch makes signed commits required on a protected branch. +// It requires admin access and branch protection to be enabled. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#add-required-signatures-of-protected-branch +func (s *RepositoriesService) RequireSignaturesOnProtectedBranch(ctx context.Context, owner, repo, branch string) (*SignaturesProtectedBranch, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_signatures", owner, repo, branch) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeSignaturePreview) + + r := new(SignaturesProtectedBranch) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// OptionalSignaturesOnProtectedBranch removes required signed commits on a given branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#remove-required-signatures-of-protected-branch +func (s *RepositoriesService) OptionalSignaturesOnProtectedBranch(ctx context.Context, owner, repo, branch string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_signatures", owner, repo, branch) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeSignaturePreview) + + return s.client.Do(ctx, req, nil) +} + // UpdateRequiredStatusChecks updates the required status checks for a given protected branch. // // GitHub API docs: https://developer.github.com/v3/repos/branches/#update-required-status-checks-of-protected-branch diff --git a/github/repos_test.go b/github/repos_test.go index 08d64412346..4109eda9321 100644 --- a/github/repos_test.go +++ b/github/repos_test.go @@ -1055,6 +1055,72 @@ func TestRepositoriesService_RemoveAdminEnforcement(t *testing.T) { } } +func TestRepositoriesService_GetSignaturesProtectedBranch(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection/required_signatures", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypeSignaturePreview) + fmt.Fprintf(w, `{"url":"/repos/o/r/branches/b/protection/required_signatures","enabled":false}`) + }) + + signature, _, err := client.Repositories.GetSignaturesProtectedBranch(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.GetSignaturesProtectedBranch returned error: %v", err) + } + + want := &SignaturesProtectedBranch{ + URL: String("/repos/o/r/branches/b/protection/required_signatures"), + Enabled: Bool(false), + } + + if !reflect.DeepEqual(signature, want) { + t.Errorf("Repositories.GetSignaturesProtectedBranch returned %+v, want %+v", signature, want) + } +} + +func TestRepositoriesService_RequireSignaturesOnProtectedBranch(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection/required_signatures", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeSignaturePreview) + fmt.Fprintf(w, `{"url":"/repos/o/r/branches/b/protection/required_signatures","enabled":true}`) + }) + + signature, _, err := client.Repositories.RequireSignaturesOnProtectedBranch(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.RequireSignaturesOnProtectedBranch returned error: %v", err) + } + + want := &SignaturesProtectedBranch{ + URL: String("/repos/o/r/branches/b/protection/required_signatures"), + Enabled: Bool(true), + } + + if !reflect.DeepEqual(signature, want) { + t.Errorf("Repositories.RequireSignaturesOnProtectedBranch returned %+v, want %+v", signature, want) + } +} + +func TestRepositoriesService_OptionalSignaturesOnProtectedBranch(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b/protection/required_signatures", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypeSignaturePreview) + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.OptionalSignaturesOnProtectedBranch(context.Background(), "o", "r", "b") + if err != nil { + t.Errorf("Repositories.OptionalSignaturesOnProtectedBranch returned error: %v", err) + } +} + func TestPullRequestReviewsEnforcementRequest_MarshalJSON_nilDismissalRestirctions(t *testing.T) { req := PullRequestReviewsEnforcementRequest{}