Repository: google/go-github
Pull request #1039 (2018-10-29 15:17:07)

Required signatures on protected branches
Added support for the preview required commit signatures for Protected Branches API.

https://developer.github.com/changes/2018-02-22-protected-branches-required-signatures/

This solves: https://github.com/google/go-github/issues/902


Patch:
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 3ec79c2c..f86ae74b 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -1292,6 +1292,14 @@ func (c *CommitFile) GetPatch() string {
 	return *c.Patch
 }
 
+// GetPreviousFilename returns the PreviousFilename field if it's non-nil, zero value otherwise.
+func (c *CommitFile) GetPreviousFilename() string {
+	if c == nil || c.PreviousFilename == nil {
+		return ""
+	}
+	return *c.PreviousFilename
+}
+
 // GetRawURL returns the RawURL field if it's non-nil, zero value otherwise.
 func (c *CommitFile) GetRawURL() string {
 	if c == nil || c.RawURL == nil {
@@ -2292,6 +2300,14 @@ func (d *DeploymentStatusRequest) GetDescription() string {
 	return *d.Description
 }
 
+// GetEnvironment returns the Environment field if it's non-nil, zero value otherwise.
+func (d *DeploymentStatusRequest) GetEnvironment() string {
+	if d == nil || d.Environment == nil {
+		return ""
+	}
+	return *d.Environment
+}
+
 // GetEnvironmentURL returns the EnvironmentURL field if it's non-nil, zero value otherwise.
 func (d *DeploymentStatusRequest) GetEnvironmentURL() string {
 	if d == nil || d.EnvironmentURL == nil {
@@ -10100,6 +10116,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 320b8104..c58d7f9b 100644
--- a/github/github.go
+++ b/github/github.go
@@ -54,6 +54,9 @@ const (
 	// https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/
 	mediaTypeDeploymentStatusPreview = "application/vnd.github.ant-man-preview+json"
 
+	// https://developer.github.com/changes/2018-10-16-deployments-environments-states-and-auto-inactive-updates/
+	mediaTypeExpandDeploymentStatusPreview = "application/vnd.github.flash-preview+json"
+
 	// https://developer.github.com/changes/2016-02-19-source-import-preview-api/
 	mediaTypeImportPreview = "application/vnd.github.barred-rock-preview"
 
@@ -122,6 +125,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 e783ccbe..d09b5cc8 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_commits.go b/github/repos_commits.go
index 04faa3ea..1acfa5ad 100644
--- a/github/repos_commits.go
+++ b/github/repos_commits.go
@@ -48,16 +48,17 @@ func (c CommitStats) String() string {
 
 // CommitFile represents a file modified in a commit.
 type CommitFile struct {
-	SHA         *string `json:"sha,omitempty"`
-	Filename    *string `json:"filename,omitempty"`
-	Additions   *int    `json:"additions,omitempty"`
-	Deletions   *int    `json:"deletions,omitempty"`
-	Changes     *int    `json:"changes,omitempty"`
-	Status      *string `json:"status,omitempty"`
-	Patch       *string `json:"patch,omitempty"`
-	BlobURL     *string `json:"blob_url,omitempty"`
-	RawURL      *string `json:"raw_url,omitempty"`
-	ContentsURL *string `json:"contents_url,omitempty"`
+	SHA              *string `json:"sha,omitempty"`
+	Filename         *string `json:"filename,omitempty"`
+	Additions        *int    `json:"additions,omitempty"`
+	Deletions        *int    `json:"deletions,omitempty"`
+	Changes          *int    `json:"changes,omitempty"`
+	Status           *string `json:"status,omitempty"`
+	Patch            *string `json:"patch,omitempty"`
+	BlobURL          *string `json:"blob_url,omitempty"`
+	RawURL           *string `json:"raw_url,omitempty"`
+	ContentsURL      *string `json:"contents_url,omitempty"`
+	PreviousFilename *string `json:"previous_filename,omitempty"`
 }
 
 func (c CommitFile) String() string {
diff --git a/github/repos_deployments.go b/github/repos_deployments.go
index 794c3232..604632e9 100644
--- a/github/repos_deployments.go
+++ b/github/repos_deployments.go
@@ -9,6 +9,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"strings"
 )
 
 // Deployment represents a deployment in a repo
@@ -116,7 +117,8 @@ func (s *RepositoriesService) CreateDeployment(ctx context.Context, owner, repo
 	}
 
 	// TODO: remove custom Accept headers when APIs fully launch.
-	req.Header.Set("Accept", mediaTypeDeploymentStatusPreview)
+	acceptHeaders := []string{mediaTypeDeploymentStatusPreview, mediaTypeExpandDeploymentStatusPreview}
+	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
 
 	d := new(Deployment)
 	resp, err := s.client.Do(ctx, req, d)
@@ -149,6 +151,7 @@ type DeploymentStatusRequest struct {
 	State          *string `json:"state,omitempty"`
 	LogURL         *string `json:"log_url,omitempty"`
 	Description    *string `json:"description,omitempty"`
+	Environment    *string `json:"environment,omitempty"`
 	EnvironmentURL *string `json:"environment_url,omitempty"`
 	AutoInactive   *bool   `json:"auto_inactive,omitempty"`
 }
@@ -189,7 +192,8 @@ func (s *RepositoriesService) GetDeploymentStatus(ctx context.Context, owner, re
 	}
 
 	// TODO: remove custom Accept headers when APIs fully launch.
-	req.Header.Set("Accept", mediaTypeDeploymentStatusPreview)
+	acceptHeaders := []string{mediaTypeDeploymentStatusPreview, mediaTypeExpandDeploymentStatusPreview}
+	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
 
 	d := new(DeploymentStatus)
 	resp, err := s.client.Do(ctx, req, d)
@@ -212,7 +216,8 @@ func (s *RepositoriesService) CreateDeploymentStatus(ctx context.Context, owner,
 	}
 
 	// TODO: remove custom Accept headers when APIs fully launch.
-	req.Header.Set("Accept", mediaTypeDeploymentStatusPreview)
+	acceptHeaders := []string{mediaTypeDeploymentStatusPreview, mediaTypeExpandDeploymentStatusPreview}
+	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
 
 	d := new(DeploymentStatus)
 	resp, err := s.client.Do(ctx, req, d)


Test patch:
diff --git a/github/repos_deployments_test.go b/github/repos_deployments_test.go
index f6f333aa..3396cf44 100644
--- a/github/repos_deployments_test.go
+++ b/github/repos_deployments_test.go
@@ -11,6 +11,7 @@ import (
 	"fmt"
 	"net/http"
 	"reflect"
+	"strings"
 	"testing"
 )
 
@@ -68,7 +69,8 @@ func TestRepositoriesService_CreateDeployment(t *testing.T) {
 		json.NewDecoder(r.Body).Decode(v)
 
 		testMethod(t, r, "POST")
-		testHeader(t, r, "Accept", mediaTypeDeploymentStatusPreview)
+		acceptHeaders := []string{mediaTypeDeploymentStatusPreview, mediaTypeExpandDeploymentStatusPreview}
+		testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", "))
 		if !reflect.DeepEqual(v, input) {
 			t.Errorf("Request body = %+v, want %+v", v, input)
 		}
@@ -113,9 +115,10 @@ func TestRepositoriesService_GetDeploymentStatus(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
+	acceptHeaders := []string{mediaTypeDeploymentStatusPreview, mediaTypeExpandDeploymentStatusPreview}
 	mux.HandleFunc("/repos/o/r/deployments/3/statuses/4", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		testHeader(t, r, "Accept", mediaTypeDeploymentStatusPreview)
+		testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", "))
 		fmt.Fprint(w, `{"id":4}`)
 	})
 
@@ -141,7 +144,8 @@ func TestRepositoriesService_CreateDeploymentStatus(t *testing.T) {
 		json.NewDecoder(r.Body).Decode(v)
 
 		testMethod(t, r, "POST")
-		testHeader(t, r, "Accept", mediaTypeDeploymentStatusPreview)
+		acceptHeaders := []string{mediaTypeDeploymentStatusPreview, mediaTypeExpandDeploymentStatusPreview}
+		testHeader(t, r, "Accept", strings.Join(acceptHeaders, ", "))
 		if !reflect.DeepEqual(v, input) {
 			t.Errorf("Request body = %+v, want %+v", v, input)
 		}
diff --git a/github/repos_test.go b/github/repos_test.go
index 08d64412..4109eda9 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{}