diff --git a/github/resource_github_branch_protection.go b/github/resource_github_branch_protection.go index 2032835768..ed2d24b300 100644 --- a/github/resource_github_branch_protection.go +++ b/github/resource_github_branch_protection.go @@ -3,6 +3,7 @@ package github import ( "context" "errors" + "fmt" "net/http" "github.com/google/go-github/github" @@ -37,9 +38,13 @@ func resourceGithubBranchProtection() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "include_admins": { - Type: schema.TypeBool, - Optional: true, - Default: false, + Type: schema.TypeBool, + Optional: true, + Default: false, + Deprecated: "Use enforce_admins instead", + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return true + }, }, "strict": { Type: schema.TypeBool, @@ -47,7 +52,7 @@ func resourceGithubBranchProtection() *schema.Resource { Default: false, }, "contexts": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{ Type: schema.TypeString, @@ -63,10 +68,29 @@ func resourceGithubBranchProtection() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "include_admins": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Deprecated: "Use enforce_admins instead", + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return true + }, + }, + "dismiss_stale_reviews": { Type: schema.TypeBool, Optional: true, Default: false, }, + "dismissal_users": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "dismissal_teams": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, }, }, @@ -77,18 +101,23 @@ func resourceGithubBranchProtection() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "users": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "teams": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, }, }, }, + "enforce_admins": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, }, } } @@ -128,54 +157,18 @@ func resourceGithubBranchProtectionRead(d *schema.ResourceData, meta interface{} d.Set("repository", r) d.Set("branch", b) + d.Set("enforce_admins", githubProtection.EnforceAdmins.Enabled) - rsc := githubProtection.RequiredStatusChecks - if rsc != nil { - d.Set("required_status_checks", []interface{}{ - map[string]interface{}{ - "include_admins": rsc.IncludeAdmins, - "strict": rsc.Strict, - "contexts": rsc.Contexts, - }, - }) - } else { - d.Set("required_status_checks", []interface{}{}) + if err := flattenRequiredStatusChecks(d, githubProtection); err != nil { + return fmt.Errorf("Error setting required_status_checks: %v", err) } - rprr := githubProtection.RequiredPullRequestReviews - if rprr != nil { - d.Set("required_pull_request_reviews", []interface{}{ - map[string]interface{}{ - "include_admins": rprr.IncludeAdmins, - }, - }) - } else { - d.Set("required_pull_request_reviews", []interface{}{}) + if err := flattenRequiredPullRequestReviews(d, githubProtection); err != nil { + return fmt.Errorf("Error setting required_pull_request_reviews: %v", err) } - restrictions := githubProtection.Restrictions - if restrictions != nil { - var userLogins []string - for _, u := range restrictions.Users { - if u.Login != nil { - userLogins = append(userLogins, *u.Login) - } - } - var teamSlugs []string - for _, t := range restrictions.Teams { - if t.Slug != nil { - teamSlugs = append(teamSlugs, *t.Slug) - } - } - - d.Set("restrictions", []interface{}{ - map[string]interface{}{ - "users": userLogins, - "teams": teamSlugs, - }, - }) - } else { - d.Set("restrictions", []interface{}{}) + if err := flattenRestrictions(d, githubProtection); err != nil { + return fmt.Errorf("Error setting restrictions: %v", err) } return nil @@ -210,74 +203,210 @@ func resourceGithubBranchProtectionDelete(d *schema.ResourceData, meta interface func buildProtectionRequest(d *schema.ResourceData) (*github.ProtectionRequest, error) { protectionRequest := new(github.ProtectionRequest) + rsc, err := expandRequiredStatusChecks(d) + if err != nil { + return nil, err + } + protectionRequest.RequiredStatusChecks = rsc + + rprr, err := expandRequiredPullRequestReviews(d) + if err != nil { + return nil, err + } + protectionRequest.RequiredPullRequestReviews = rprr + + res, err := expandRestrictions(d) + if err != nil { + return nil, err + } + protectionRequest.Restrictions = res + + protectionRequest.EnforceAdmins = d.Get("enforce_admins").(bool) + + return protectionRequest, nil +} + +func flattenRequiredStatusChecks(d *schema.ResourceData, protection *github.Protection) error { + rsc := protection.RequiredStatusChecks + if rsc != nil { + contexts := make([]interface{}, 0, len(rsc.Contexts)) + for _, c := range rsc.Contexts { + contexts = append(contexts, c) + } + + if err := d.Set("required_status_checks", []interface{}{ + map[string]interface{}{ + "strict": rsc.Strict, + "contexts": schema.NewSet(schema.HashString, contexts), + }, + }); err != nil { + return err + } + } else { + d.Set("required_status_checks", []interface{}{}) + } + + return nil +} + +func flattenRequiredPullRequestReviews(d *schema.ResourceData, protection *github.Protection) error { + rprr := protection.RequiredPullRequestReviews + if rprr != nil { + users := make([]interface{}, 0, len(rprr.DismissalRestrictions.Users)) + for _, u := range rprr.DismissalRestrictions.Users { + if u.Login != nil { + users = append(users, *u.Login) + } + } + + teams := make([]interface{}, 0, len(rprr.DismissalRestrictions.Teams)) + for _, t := range rprr.DismissalRestrictions.Teams { + if t.Slug != nil { + teams = append(teams, *t.Slug) + } + } + + if err := d.Set("required_pull_request_reviews", []interface{}{ + map[string]interface{}{ + "dismiss_stale_reviews": rprr.DismissStaleReviews, + "dismissal_users": schema.NewSet(schema.HashString, users), + "dismissal_teams": schema.NewSet(schema.HashString, teams), + }, + }); err != nil { + return err + } + } else { + d.Set("required_pull_request_reviews", []interface{}{}) + } + + return nil +} + +func flattenRestrictions(d *schema.ResourceData, protection *github.Protection) error { + restrictions := protection.Restrictions + if restrictions != nil { + users := make([]interface{}, 0, len(restrictions.Users)) + for _, u := range restrictions.Users { + if u.Login != nil { + users = append(users, *u.Login) + } + } + + teams := make([]interface{}, 0, len(restrictions.Teams)) + for _, t := range restrictions.Teams { + if t.Slug != nil { + teams = append(teams, *t.Slug) + } + } + + if err := d.Set("restrictions", []interface{}{ + map[string]interface{}{ + "users": schema.NewSet(schema.HashString, users), + "teams": schema.NewSet(schema.HashString, teams), + }, + }); err != nil { + return fmt.Errorf("Error setting restrictions: %v", err) + } + } else { + d.Set("restrictions", []interface{}{}) + } + + return nil +} + +func expandRequiredStatusChecks(d *schema.ResourceData) (*github.RequiredStatusChecks, error) { if v, ok := d.GetOk("required_status_checks"); ok { vL := v.([]interface{}) if len(vL) > 1 { return nil, errors.New("cannot specify required_status_checks more than one time") } + rsc := new(github.RequiredStatusChecks) for _, v := range vL { + // List can only have one item, safe to early return here + if v == nil { + return nil, nil + } m := v.(map[string]interface{}) - - rsc := new(github.RequiredStatusChecks) - rsc.IncludeAdmins = m["include_admins"].(bool) rsc.Strict = m["strict"].(bool) - rsc.Contexts = []string{} - if contexts, ok := m["contexts"].([]interface{}); ok { - for _, c := range contexts { - rsc.Contexts = append(rsc.Contexts, c.(string)) - } - } - - protectionRequest.RequiredStatusChecks = rsc + contexts := expandNestedSet(m, "contexts") + rsc.Contexts = contexts } + return rsc, nil } + return nil, nil +} + +func expandRequiredPullRequestReviews(d *schema.ResourceData) (*github.PullRequestReviewsEnforcementRequest, error) { if v, ok := d.GetOk("required_pull_request_reviews"); ok { vL := v.([]interface{}) if len(vL) > 1 { return nil, errors.New("cannot specify required_pull_request_reviews more than one time") } + rprr := new(github.PullRequestReviewsEnforcementRequest) + drr := new(github.DismissalRestrictionsRequest) + for _, v := range vL { + // List can only have one item, safe to early return here + if v == nil { + return nil, nil + } m := v.(map[string]interface{}) - rprr := new(github.RequiredPullRequestReviews) - rprr.IncludeAdmins = m["include_admins"].(bool) + users := expandNestedSet(m, "dismissal_users") + drr.Users = users + teams := expandNestedSet(m, "dismissal_teams") + drr.Teams = teams - protectionRequest.RequiredPullRequestReviews = rprr + rprr.DismissalRestrictionsRequest = drr + rprr.DismissStaleReviews = m["dismiss_stale_reviews"].(bool) } + + return rprr, nil } + return nil, nil +} + +func expandRestrictions(d *schema.ResourceData) (*github.BranchRestrictionsRequest, error) { if v, ok := d.GetOk("restrictions"); ok { vL := v.([]interface{}) if len(vL) > 1 { return nil, errors.New("cannot specify restrictions more than one time") } + restrictions := new(github.BranchRestrictionsRequest) for _, v := range vL { + // Restrictions only have set attributes nested, need to return nil values for these. + // The API won't initialize these as nil + if v == nil { + restrictions.Users = []string{} + restrictions.Teams = []string{} + return restrictions, nil + } m := v.(map[string]interface{}) - restrictions := new(github.BranchRestrictionsRequest) - - restrictions.Users = []string{} - if users, ok := m["users"].([]interface{}); ok { - for _, u := range users { - restrictions.Users = append(restrictions.Users, u.(string)) - } - } + users := expandNestedSet(m, "users") + restrictions.Users = users + teams := expandNestedSet(m, "teams") + restrictions.Teams = teams + } + return restrictions, nil + } - restrictions.Teams = []string{} - if teams, ok := m["teams"].([]interface{}); ok { - for _, t := range teams { - restrictions.Teams = append(restrictions.Teams, t.(string)) - } - } + return nil, nil +} - protectionRequest.Restrictions = restrictions +func expandNestedSet(m map[string]interface{}, target string) []string { + res := []string{} + if v, ok := m[target]; ok { + vL := v.(*schema.Set).List() + for _, v := range vL { + res = append(res, v.(string)) } } - - return protectionRequest, nil + return res } diff --git a/github/resource_github_branch_protection_test.go b/github/resource_github_branch_protection_test.go index 89f4a7bff8..58560b459e 100644 --- a/github/resource_github_branch_protection_test.go +++ b/github/resource_github_branch_protection_test.go @@ -3,13 +3,13 @@ package github import ( "context" "fmt" - "reflect" "testing" "github.com/google/go-github/github" "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "github.com/kylelemons/godebug/pretty" ) func TestAccGithubBranchProtection_basic(t *testing.T) { @@ -27,17 +27,18 @@ func TestAccGithubBranchProtection_basic(t *testing.T) { Config: testAccGithubBranchProtectionConfig(repoName), Check: resource.ComposeTestCheckFunc( testAccCheckGithubProtectedBranchExists("github_branch_protection.master", repoName+":master", &protection), - testAccCheckGithubBranchProtectionRequiredStatusChecks(&protection, true, true, []string{"github/foo"}), + testAccCheckGithubBranchProtectionRequiredStatusChecks(&protection, true, []string{"github/foo"}), testAccCheckGithubBranchProtectionRestrictions(&protection, []string{testUser}, []string{}), + testAccCheckGithubBranchProtectionPullRequestReviews(&protection, true, []string{testUser}, []string{}), resource.TestCheckResourceAttr("github_branch_protection.master", "repository", repoName), resource.TestCheckResourceAttr("github_branch_protection.master", "branch", "master"), - resource.TestCheckResourceAttr("github_branch_protection.master", "required_status_checks.0.include_admins", "true"), + resource.TestCheckResourceAttr("github_branch_protection.master", "enforce_admins", "true"), resource.TestCheckResourceAttr("github_branch_protection.master", "required_status_checks.0.strict", "true"), resource.TestCheckResourceAttr("github_branch_protection.master", "required_status_checks.0.contexts.#", "1"), - resource.TestCheckResourceAttr("github_branch_protection.master", "required_status_checks.0.contexts.0", "github/foo"), - resource.TestCheckResourceAttr("github_branch_protection.master", "required_pull_request_reviews.0.include_admins", "true"), + resource.TestCheckResourceAttr("github_branch_protection.master", "required_pull_request_reviews.0.dismiss_stale_reviews", "true"), + resource.TestCheckResourceAttr("github_branch_protection.master", "required_pull_request_reviews.0.dismissal_users.#", "1"), + resource.TestCheckResourceAttr("github_branch_protection.master", "required_pull_request_reviews.0.dismissal_teams.#", "0"), resource.TestCheckResourceAttr("github_branch_protection.master", "restrictions.0.users.#", "1"), - resource.TestCheckResourceAttr("github_branch_protection.master", "restrictions.0.users.0", testUser), resource.TestCheckResourceAttr("github_branch_protection.master", "restrictions.0.teams.#", "0"), ), }, @@ -45,14 +46,13 @@ func TestAccGithubBranchProtection_basic(t *testing.T) { Config: testAccGithubBranchProtectionUpdateConfig(repoName), Check: resource.ComposeTestCheckFunc( testAccCheckGithubProtectedBranchExists("github_branch_protection.master", repoName+":master", &protection), - testAccCheckGithubBranchProtectionRequiredStatusChecks(&protection, false, false, []string{"github/bar"}), + testAccCheckGithubBranchProtectionRequiredStatusChecks(&protection, false, []string{"github/bar"}), testAccCheckGithubBranchProtectionNoRestrictionsExist(&protection), + testAccCheckGithubBranchProtectionNoPullRequestReviewsExist(&protection), resource.TestCheckResourceAttr("github_branch_protection.master", "repository", repoName), resource.TestCheckResourceAttr("github_branch_protection.master", "branch", "master"), - resource.TestCheckResourceAttr("github_branch_protection.master", "required_status_checks.0.include_admins", "false"), resource.TestCheckResourceAttr("github_branch_protection.master", "required_status_checks.0.strict", "false"), resource.TestCheckResourceAttr("github_branch_protection.master", "required_status_checks.0.contexts.#", "1"), - resource.TestCheckResourceAttr("github_branch_protection.master", "required_status_checks.0.contexts.0", "github/bar"), resource.TestCheckResourceAttr("github_branch_protection.master", "required_pull_request_reviews.#", "0"), resource.TestCheckResourceAttr("github_branch_protection.master", "restrictions.#", "0"), ), @@ -61,6 +61,34 @@ func TestAccGithubBranchProtection_basic(t *testing.T) { }) } +// See https://github.com/terraform-providers/terraform-provider-github/issues/8 +func TestAccGithubBranchProtection_emptyItems(t *testing.T) { + var protection github.Protection + + rString := acctest.RandString(5) + repoName := fmt.Sprintf("tf-acc-test-branch-prot-%s", rString) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccGithubBranchProtectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGithubBranchProtectionConfigEmptyItems(repoName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGithubProtectedBranchExists("github_branch_protection.master", repoName+":master", &protection), + resource.TestCheckResourceAttr("github_branch_protection.master", "repository", repoName), + resource.TestCheckResourceAttr("github_branch_protection.master", "branch", "master"), + resource.TestCheckResourceAttr("github_branch_protection.master", "enforce_admins", "true"), + resource.TestCheckResourceAttr("github_branch_protection.master", "required_status_checks.#", "1"), + resource.TestCheckResourceAttr("github_branch_protection.master", "required_pull_request_reviews.#", "1"), + resource.TestCheckResourceAttr("github_branch_protection.master", "restrictions.#", "1"), + ), + }, + }, + }) +} + func TestAccGithubBranchProtection_importBasic(t *testing.T) { rString := acctest.RandString(5) @@ -106,22 +134,19 @@ func testAccCheckGithubProtectedBranchExists(n, id string, protection *github.Pr } } -func testAccCheckGithubBranchProtectionRequiredStatusChecks(protection *github.Protection, expectedIncludeAdmins bool, expectedStrict bool, expectedContexts []string) resource.TestCheckFunc { +func testAccCheckGithubBranchProtectionRequiredStatusChecks(protection *github.Protection, expectedStrict bool, expectedContexts []string) resource.TestCheckFunc { return func(s *terraform.State) error { rsc := protection.RequiredStatusChecks if rsc == nil { return fmt.Errorf("Expected RequiredStatusChecks to be present, but was nil") } - if rsc.IncludeAdmins != expectedIncludeAdmins { - return fmt.Errorf("Expected RequiredStatusChecks.IncludeAdmins to be %v, got %v", expectedIncludeAdmins, rsc.IncludeAdmins) - } if rsc.Strict != expectedStrict { return fmt.Errorf("Expected RequiredStatusChecks.Strict to be %v, got %v", expectedStrict, rsc.Strict) } - if !reflect.DeepEqual(rsc.Contexts, expectedContexts) { - return fmt.Errorf("Expected RequiredStatusChecks.Contexts to be %v, got %v", expectedContexts, rsc.Contexts) + if diff := pretty.Compare(rsc.Contexts, expectedContexts); diff != "" { + return fmt.Errorf("diff %q: (-got +want)\n%s", "contexts", diff) } return nil @@ -139,16 +164,47 @@ func testAccCheckGithubBranchProtectionRestrictions(protection *github.Protectio for _, u := range restrictions.Users { userLogins = append(userLogins, *u.Login) } - if !reflect.DeepEqual(userLogins, expectedUserLogins) { - return fmt.Errorf("Expected Restrictions.Users to be %v, got %v", expectedUserLogins, userLogins) + if diff := pretty.Compare(userLogins, expectedUserLogins); diff != "" { + return fmt.Errorf("diff %q: (-got +want)\n%s", "restricted users", diff) } teamLogins := []string{} for _, t := range restrictions.Teams { teamLogins = append(teamLogins, *t.Name) } - if !reflect.DeepEqual(teamLogins, expectedTeamNames) { - return fmt.Errorf("Expected Restrictions.Teams to be %v, got %v", expectedTeamNames, teamLogins) + if diff := pretty.Compare(teamLogins, expectedTeamNames); diff != "" { + return fmt.Errorf("diff %q: (-got +want)\n%s", "restricted teams", diff) + } + + return nil + } +} + +func testAccCheckGithubBranchProtectionPullRequestReviews(protection *github.Protection, expectedStale bool, expectedUsers, expectedTeams []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + reviews := protection.RequiredPullRequestReviews + if reviews == nil { + return fmt.Errorf("Expected Pull Request Reviews to be present, but was nil") + } + + if reviews.DismissStaleReviews != expectedStale { + return fmt.Errorf("Expected `dismiss_state_reviews` to be %t, got %t", expectedStale, reviews.DismissStaleReviews) + } + + users := []string{} + for _, u := range reviews.DismissalRestrictions.Users { + users = append(users, *u.Login) + } + if diff := pretty.Compare(users, expectedUsers); diff != "" { + return fmt.Errorf("diff %q: (-got +want)\n%s", "dismissal_users", diff) + } + + teams := []string{} + for _, t := range reviews.DismissalRestrictions.Teams { + teams = append(users, *t.Slug) + } + if diff := pretty.Compare(teams, expectedTeams); diff != "" { + return fmt.Errorf("diff %q: (-got +want)\n%s", "dismissal_teams", diff) } return nil @@ -166,6 +222,16 @@ func testAccCheckGithubBranchProtectionNoRestrictionsExist(protection *github.Pr } } +func testAccCheckGithubBranchProtectionNoPullRequestReviewsExist(protection *github.Protection) resource.TestCheckFunc { + return func(s *terraform.State) error { + if protection.RequiredPullRequestReviews != nil { + return fmt.Errorf("Expected Pull Request reviews to be nil, but was %v", protection.RequiredPullRequestReviews) + } + + return nil + } +} + func testAccGithubBranchProtectionDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*Organization).client @@ -202,22 +268,23 @@ resource "github_repository" "test" { resource "github_branch_protection" "master" { repository = "${github_repository.test.name}" branch = "master" + enforce_admins = true required_status_checks = { - include_admins = true strict = true contexts = ["github/foo"] } required_pull_request_reviews { - include_admins = true + dismiss_stale_reviews = true + dismissal_users = ["%s"] } restrictions { users = ["%s"] } } -`, repoName, repoName, testUser) +`, repoName, repoName, testUser, testUser) } func testAccGithubBranchProtectionUpdateConfig(repoName string) string { @@ -233,10 +300,34 @@ resource "github_branch_protection" "master" { branch = "master" required_status_checks = { - include_admins = false strict = false contexts = ["github/bar"] } } `, repoName, repoName) } + +func testAccGithubBranchProtectionConfigEmptyItems(repoName string) string { + return fmt.Sprintf(` +resource "github_repository" "test" { + name = "%s" + description = "Terraform Acceptance Test %s" + auto_init = true +} + +resource "github_branch_protection" "master" { + repository = "${github_repository.test.name}" + branch = "master" + enforce_admins = true + + required_status_checks = { + } + + required_pull_request_reviews { + } + + restrictions { + } +} +`, repoName, repoName) +} diff --git a/vendor/github.com/google/go-github/github/github-accessors.go b/vendor/github.com/google/go-github/github/github-accessors.go index 0e6d14440f..9f47359505 100644 --- a/vendor/github.com/google/go-github/github/github-accessors.go +++ b/vendor/github.com/google/go-github/github/github-accessors.go @@ -4156,6 +4156,14 @@ func (p *PullRequestReviewRequest) GetEvent() string { return *p.Event } +// GetDismissStaleReviews returns the DismissStaleReviews field if it's non-nil, zero value otherwise. +func (p *PullRequestReviewsEnforcementUpdate) GetDismissStaleReviews() bool { + if p == nil || p.DismissStaleReviews == nil { + return false + } + return *p.DismissStaleReviews +} + // GetBase returns the Base field if it's non-nil, zero value otherwise. func (p *pullRequestUpdate) GetBase() string { if p == nil || p.Base == nil { diff --git a/vendor/github.com/google/go-github/github/repos.go b/vendor/github.com/google/go-github/github/repos.go index c3629daa5c..38bbc15d5f 100644 --- a/vendor/github.com/google/go-github/github/repos.go +++ b/vendor/github.com/google/go-github/github/repos.go @@ -7,6 +7,7 @@ package github import ( "context" + "encoding/json" "fmt" "strings" ) @@ -532,25 +533,22 @@ type Branch struct { // Protection represents a repository branch's protection. type Protection struct { - RequiredStatusChecks *RequiredStatusChecks `json:"required_status_checks"` - RequiredPullRequestReviews *RequiredPullRequestReviews `json:"required_pull_request_reviews"` - EnforceAdmins *AdminEnforcement `json:"enforce_admins"` - Restrictions *BranchRestrictions `json:"restrictions"` + RequiredStatusChecks *RequiredStatusChecks `json:"required_status_checks"` + RequiredPullRequestReviews *PullRequestReviewsEnforcement `json:"required_pull_request_reviews"` + EnforceAdmins *AdminEnforcement `json:"enforce_admins"` + Restrictions *BranchRestrictions `json:"restrictions"` } // ProtectionRequest represents a request to create/edit a branch's protection. type ProtectionRequest struct { - RequiredStatusChecks *RequiredStatusChecks `json:"required_status_checks"` - RequiredPullRequestReviews *RequiredPullRequestReviews `json:"required_pull_request_reviews"` - EnforceAdmins bool `json:"enforce_admins"` - Restrictions *BranchRestrictionsRequest `json:"restrictions"` + RequiredStatusChecks *RequiredStatusChecks `json:"required_status_checks"` + RequiredPullRequestReviews *PullRequestReviewsEnforcementRequest `json:"required_pull_request_reviews"` + EnforceAdmins bool `json:"enforce_admins"` + Restrictions *BranchRestrictionsRequest `json:"restrictions"` } // RequiredStatusChecks represents the protection status of a individual branch. type RequiredStatusChecks struct { - // Enforce required status checks for repository administrators. (Required.) - // Deprecated: Use EnforceAdmins instead. - IncludeAdmins bool `json:"include_admins"` // Require branches to be up to date before merging. (Required.) Strict bool `json:"strict"` // The list of status checks to require in order to merge into this @@ -558,11 +556,55 @@ type RequiredStatusChecks struct { Contexts []string `json:"contexts"` } -// RequiredPullRequestReviews represents the protection configuration for pull requests. -type RequiredPullRequestReviews struct { - // Enforce pull request reviews for repository administrators. (Required.) - // Deprecated: Use EnforceAdmins instead. - IncludeAdmins bool `json:"include_admins"` +// PullRequestReviewsEnforcement represents the pull request reviews enforcement of a protected branch. +type PullRequestReviewsEnforcement struct { + // Specifies which users and teams can dismiss pull requets reviews. + DismissalRestrictions DismissalRestrictions `json:"dismissal_restrictions"` + // Specifies if approved reviews are dismissed automatically, when a new commit is pushed. + DismissStaleReviews bool `json:"dismiss_stale_reviews"` +} + +// PullRequestReviewsEnforcementRequest represents request to set the pull request review +// enforcement of a protected branch. It is separate from PullRequestReviewsEnforcement above +// because the request structure is different from the response structure. +type PullRequestReviewsEnforcementRequest struct { + // Specifies which users and teams should be allowed to dismiss pull requets reviews. Can be nil to disable the restrictions. + DismissalRestrictionsRequest *DismissalRestrictionsRequest `json:"dismissal_restrictions"` + // Specifies if approved reviews can be dismissed automatically, when a new commit is pushed. (Required) + DismissStaleReviews bool `json:"dismiss_stale_reviews"` +} + +// MarshalJSON implements the json.Marshaler interface. +// Converts nil value of PullRequestReviewsEnforcementRequest.DismissalRestrictionsRequest to empty array +func (req PullRequestReviewsEnforcementRequest) MarshalJSON() ([]byte, error) { + if req.DismissalRestrictionsRequest == nil { + newReq := struct { + R []interface{} `json:"dismissal_restrictions"` + D bool `json:"dismiss_stale_reviews"` + }{ + R: []interface{}{}, + D: req.DismissStaleReviews, + } + return json.Marshal(newReq) + } + newReq := struct { + R *DismissalRestrictionsRequest `json:"dismissal_restrictions"` + D bool `json:"dismiss_stale_reviews"` + }{ + R: req.DismissalRestrictionsRequest, + D: req.DismissStaleReviews, + } + return json.Marshal(newReq) +} + +// PullRequestReviewsEnforcementUpdate represents request to patch the pull request review +// enforcement of a protected branch. It is separate from PullRequestReviewsEnforcementRequest above +// because the patch request does not require all fields to be initialized. +type PullRequestReviewsEnforcementUpdate struct { + // Specifies which users and teams can dismiss pull requets reviews. Can be ommitted. + DismissalRestrictionsRequest *DismissalRestrictionsRequest `json:"dismissal_restrictions,omitempty"` + // Specifies if approved reviews can be dismissed automatically, when a new commit is pushed. Can be ommited. + DismissStaleReviews *bool `json:"dismiss_stale_reviews,omitempty"` } // AdminEnforcement represents the configuration to enforce required status checks for repository administrators. @@ -591,6 +633,25 @@ type BranchRestrictionsRequest struct { Teams []string `json:"teams"` } +// DismissalRestrictions specifies which users and teams can dismiss pull request reviews. +type DismissalRestrictions struct { + // The list of users who can dimiss pull request reviews. + Users []*User `json:"users"` + // The list of teams which can dismiss pull request reviews. + Teams []*Team `json:"teams"` +} + +// DismissalRestrictionsRequest represents the request to create/edit the +// restriction to allows only specific users or teams to dimiss pull request reviews. It is +// separate from DismissalRestrictions above because the request structure is +// different from the response structure. +type DismissalRestrictionsRequest struct { + // The list of user logins who can dismiss pull request reviews. (Required; use []string{} instead of nil for empty list.) + Users []string `json:"users"` + // The list of team slugs which can dismiss pull request reviews. (Required; use []string{} instead of nil for empty list.) + Teams []string `json:"teams"` +} + // ListBranches lists branches for the specified repository. // // GitHub API docs: https://developer.github.com/v3/repos/#list-branches @@ -761,3 +822,153 @@ func (s *RepositoriesService) License(ctx context.Context, owner, repo string) ( return r, resp, nil } + +// GetPullRequestReviewEnforcement gets pull request review enforcement of a protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#get-pull-request-review-enforcement-of-protected-branch +func (s *RepositoriesService) GetPullRequestReviewEnforcement(ctx context.Context, owner, repo, branch string) (*PullRequestReviewsEnforcement, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_pull_request_reviews", 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", mediaTypeProtectedBranchesPreview) + + r := new(PullRequestReviewsEnforcement) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// UpdatePullRequestReviewEnforcement patches pull request review enforcement of a protected branch. +// It requires admin access and branch protection to be enabled. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#update-pull-request-review-enforcement-of-protected-branch +func (s *RepositoriesService) UpdatePullRequestReviewEnforcement(ctx context.Context, owner, repo, branch string, patch *PullRequestReviewsEnforcementUpdate) (*PullRequestReviewsEnforcement, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_pull_request_reviews", owner, repo, branch) + req, err := s.client.NewRequest("PATCH", u, patch) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeProtectedBranchesPreview) + + r := new(PullRequestReviewsEnforcement) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// DisableDismissalRestrictions disables dismissal restrictions of a protected branch. +// It requires admin access and branch protection to be enabled. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#update-pull-request-review-enforcement-of-protected-branch +func (s *RepositoriesService) DisableDismissalRestrictions(ctx context.Context, owner, repo, branch string) (*PullRequestReviewsEnforcement, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_pull_request_reviews", owner, repo, branch) + + data := struct { + R []interface{} `json:"dismissal_restrictions"` + }{[]interface{}{}} + + req, err := s.client.NewRequest("PATCH", u, data) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypeProtectedBranchesPreview) + + r := new(PullRequestReviewsEnforcement) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// RemovePullRequestReviewEnforcement removes pull request enforcement of a protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#remove-pull-request-review-enforcement-of-protected-branch +func (s *RepositoriesService) RemovePullRequestReviewEnforcement(ctx context.Context, owner, repo, branch string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_pull_request_reviews", 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", mediaTypeProtectedBranchesPreview) + + return s.client.Do(ctx, req, nil) +} + +// GetAdminEnforcement gets admin enforcement information of a protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#get-admin-enforcement-of-protected-branch +func (s *RepositoriesService) GetAdminEnforcement(ctx context.Context, owner, repo, branch string) (*AdminEnforcement, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/enforce_admins", 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", mediaTypeProtectedBranchesPreview) + + r := new(AdminEnforcement) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// AddAdminEnforcement adds admin enforcement to a protected branch. +// It requires admin access and branch protection to be enabled. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#add-admin-enforcement-of-protected-branch +func (s *RepositoriesService) AddAdminEnforcement(ctx context.Context, owner, repo, branch string) (*AdminEnforcement, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/enforce_admins", 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", mediaTypeProtectedBranchesPreview) + + r := new(AdminEnforcement) + resp, err := s.client.Do(ctx, req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// RemoveAdminEnforcement removes admin enforcement from a protected branch. +// +// GitHub API docs: https://developer.github.com/v3/repos/branches/#remove-admin-enforcement-of-protected-branch +func (s *RepositoriesService) RemoveAdminEnforcement(ctx context.Context, owner, repo, branch string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/enforce_admins", 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", mediaTypeProtectedBranchesPreview) + + return s.client.Do(ctx, req, nil) +} diff --git a/vendor/github.com/kylelemons/godebug/LICENSE b/vendor/github.com/kylelemons/godebug/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/kylelemons/godebug/diff/diff.go b/vendor/github.com/kylelemons/godebug/diff/diff.go new file mode 100644 index 0000000000..200e596c62 --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/diff/diff.go @@ -0,0 +1,186 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package diff implements a linewise diff algorithm. +package diff + +import ( + "bytes" + "fmt" + "strings" +) + +// Chunk represents a piece of the diff. A chunk will not have both added and +// deleted lines. Equal lines are always after any added or deleted lines. +// A Chunk may or may not have any lines in it, especially for the first or last +// chunk in a computation. +type Chunk struct { + Added []string + Deleted []string + Equal []string +} + +func (c *Chunk) empty() bool { + return len(c.Added) == 0 && len(c.Deleted) == 0 && len(c.Equal) == 0 +} + +// Diff returns a string containing a line-by-line unified diff of the linewise +// changes required to make A into B. Each line is prefixed with '+', '-', or +// ' ' to indicate if it should be added, removed, or is correct respectively. +func Diff(A, B string) string { + aLines := strings.Split(A, "\n") + bLines := strings.Split(B, "\n") + + chunks := DiffChunks(aLines, bLines) + + buf := new(bytes.Buffer) + for _, c := range chunks { + for _, line := range c.Added { + fmt.Fprintf(buf, "+%s\n", line) + } + for _, line := range c.Deleted { + fmt.Fprintf(buf, "-%s\n", line) + } + for _, line := range c.Equal { + fmt.Fprintf(buf, " %s\n", line) + } + } + return strings.TrimRight(buf.String(), "\n") +} + +// DiffChunks uses an O(D(N+M)) shortest-edit-script algorithm +// to compute the edits required from A to B and returns the +// edit chunks. +func DiffChunks(a, b []string) []Chunk { + // algorithm: http://www.xmailserver.org/diff2.pdf + + // We'll need these quantities a lot. + alen, blen := len(a), len(b) // M, N + + // At most, it will require len(a) deletions and len(b) additions + // to transform a into b. + maxPath := alen + blen // MAX + if maxPath == 0 { + // degenerate case: two empty lists are the same + return nil + } + + // Store the endpoint of the path for diagonals. + // We store only the a index, because the b index on any diagonal + // (which we know during the loop below) is aidx-diag. + // endpoint[maxPath] represents the 0 diagonal. + // + // Stated differently: + // endpoint[d] contains the aidx of a furthest reaching path in diagonal d + endpoint := make([]int, 2*maxPath+1) // V + + saved := make([][]int, 0, 8) // Vs + save := func() { + dup := make([]int, len(endpoint)) + copy(dup, endpoint) + saved = append(saved, dup) + } + + var editDistance int // D +dLoop: + for editDistance = 0; editDistance <= maxPath; editDistance++ { + // The 0 diag(onal) represents equality of a and b. Each diagonal to + // the left is numbered one lower, to the right is one higher, from + // -alen to +blen. Negative diagonals favor differences from a, + // positive diagonals favor differences from b. The edit distance to a + // diagonal d cannot be shorter than d itself. + // + // The iterations of this loop cover either odds or evens, but not both, + // If odd indices are inputs, even indices are outputs and vice versa. + for diag := -editDistance; diag <= editDistance; diag += 2 { // k + var aidx int // x + switch { + case diag == -editDistance: + // This is a new diagonal; copy from previous iter + aidx = endpoint[maxPath-editDistance+1] + 0 + case diag == editDistance: + // This is a new diagonal; copy from previous iter + aidx = endpoint[maxPath+editDistance-1] + 1 + case endpoint[maxPath+diag+1] > endpoint[maxPath+diag-1]: + // diagonal d+1 was farther along, so use that + aidx = endpoint[maxPath+diag+1] + 0 + default: + // diagonal d-1 was farther (or the same), so use that + aidx = endpoint[maxPath+diag-1] + 1 + } + // On diagonal d, we can compute bidx from aidx. + bidx := aidx - diag // y + // See how far we can go on this diagonal before we find a difference. + for aidx < alen && bidx < blen && a[aidx] == b[bidx] { + aidx++ + bidx++ + } + // Store the end of the current edit chain. + endpoint[maxPath+diag] = aidx + // If we've found the end of both inputs, we're done! + if aidx >= alen && bidx >= blen { + save() // save the final path + break dLoop + } + } + save() // save the current path + } + if editDistance == 0 { + return nil + } + chunks := make([]Chunk, editDistance+1) + + x, y := alen, blen + for d := editDistance; d > 0; d-- { + endpoint := saved[d] + diag := x - y + insert := diag == -d || (diag != d && endpoint[maxPath+diag-1] < endpoint[maxPath+diag+1]) + + x1 := endpoint[maxPath+diag] + var x0, xM, kk int + if insert { + kk = diag + 1 + x0 = endpoint[maxPath+kk] + xM = x0 + } else { + kk = diag - 1 + x0 = endpoint[maxPath+kk] + xM = x0 + 1 + } + y0 := x0 - kk + + var c Chunk + if insert { + c.Added = b[y0:][:1] + } else { + c.Deleted = a[x0:][:1] + } + if xM < x1 { + c.Equal = a[xM:][:x1-xM] + } + + x, y = x0, y0 + chunks[d] = c + } + if x > 0 { + chunks[0].Equal = a[:x] + } + if chunks[0].empty() { + chunks = chunks[1:] + } + if len(chunks) == 0 { + return nil + } + return chunks +} diff --git a/vendor/github.com/kylelemons/godebug/pretty/doc.go b/vendor/github.com/kylelemons/godebug/pretty/doc.go new file mode 100644 index 0000000000..03b5718a70 --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/doc.go @@ -0,0 +1,25 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package pretty pretty-prints Go structures. +// +// This package uses reflection to examine a Go value and can +// print out in a nice, aligned fashion. It supports three +// modes (normal, compact, and extended) for advanced use. +// +// See the Reflect and Print examples for what the output looks like. +package pretty + +// TODO: +// - Catch cycles diff --git a/vendor/github.com/kylelemons/godebug/pretty/public.go b/vendor/github.com/kylelemons/godebug/pretty/public.go new file mode 100644 index 0000000000..948691abff --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/public.go @@ -0,0 +1,153 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pretty + +import ( + "bytes" + "fmt" + "io" + "net" + "reflect" + "time" + + "github.com/kylelemons/godebug/diff" +) + +// A Config represents optional configuration parameters for formatting. +// +// Some options, notably ShortList, dramatically increase the overhead +// of pretty-printing a value. +type Config struct { + // Verbosity options + Compact bool // One-line output. Overrides Diffable. + Diffable bool // Adds extra newlines for more easily diffable output. + + // Field and value options + IncludeUnexported bool // Include unexported fields in output + PrintStringers bool // Call String on a fmt.Stringer + PrintTextMarshalers bool // Call MarshalText on an encoding.TextMarshaler + SkipZeroFields bool // Skip struct fields that have a zero value. + + // Output transforms + ShortList int // Maximum character length for short lists if nonzero. + + // Type-specific overrides + // + // Formatter maps a type to a function that will provide a one-line string + // representation of the input value. Conceptually: + // Formatter[reflect.TypeOf(v)](v) = "v as a string" + // + // Note that the first argument need not explicitly match the type, it must + // merely be callable with it. + // + // When processing an input value, if its type exists as a key in Formatter: + // 1) If the value is nil, no stringification is performed. + // This allows overriding of PrintStringers and PrintTextMarshalers. + // 2) The value will be called with the input as its only argument. + // The function must return a string as its first return value. + // + // In addition to func literals, two common values for this will be: + // fmt.Sprint (function) func Sprint(...interface{}) string + // Type.String (method) func (Type) String() string + // + // Note that neither of these work if the String method is a pointer + // method and the input will be provided as a value. In that case, + // use a function that calls .String on the formal value parameter. + Formatter map[reflect.Type]interface{} +} + +// Default Config objects +var ( + // DefaultFormatter is the default set of overrides for stringification. + DefaultFormatter = map[reflect.Type]interface{}{ + reflect.TypeOf(time.Time{}): fmt.Sprint, + reflect.TypeOf(net.IP{}): fmt.Sprint, + reflect.TypeOf((*error)(nil)).Elem(): fmt.Sprint, + } + + // CompareConfig is the default configuration used for Compare. + CompareConfig = &Config{ + Diffable: true, + IncludeUnexported: true, + Formatter: DefaultFormatter, + } + + // DefaultConfig is the default configuration used for all other top-level functions. + DefaultConfig = &Config{ + Formatter: DefaultFormatter, + } +) + +func (cfg *Config) fprint(buf *bytes.Buffer, vals ...interface{}) { + for i, val := range vals { + if i > 0 { + buf.WriteByte('\n') + } + cfg.val2node(reflect.ValueOf(val)).WriteTo(buf, "", cfg) + } +} + +// Print writes the DefaultConfig representation of the given values to standard output. +func Print(vals ...interface{}) { + DefaultConfig.Print(vals...) +} + +// Print writes the configured presentation of the given values to standard output. +func (cfg *Config) Print(vals ...interface{}) { + fmt.Println(cfg.Sprint(vals...)) +} + +// Sprint returns a string representation of the given value according to the DefaultConfig. +func Sprint(vals ...interface{}) string { + return DefaultConfig.Sprint(vals...) +} + +// Sprint returns a string representation of the given value according to cfg. +func (cfg *Config) Sprint(vals ...interface{}) string { + buf := new(bytes.Buffer) + cfg.fprint(buf, vals...) + return buf.String() +} + +// Fprint writes the representation of the given value to the writer according to the DefaultConfig. +func Fprint(w io.Writer, vals ...interface{}) (n int64, err error) { + return DefaultConfig.Fprint(w, vals...) +} + +// Fprint writes the representation of the given value to the writer according to the cfg. +func (cfg *Config) Fprint(w io.Writer, vals ...interface{}) (n int64, err error) { + buf := new(bytes.Buffer) + cfg.fprint(buf, vals...) + return buf.WriteTo(w) +} + +// Compare returns a string containing a line-by-line unified diff of the +// values in a and b, using the CompareConfig. +// +// Each line in the output is prefixed with '+', '-', or ' ' to indicate which +// side it's from. Lines from the a side are marked with '-', lines from the +// b side are marked with '+' and lines that are the same on both sides are +// marked with ' '. +func Compare(a, b interface{}) string { + return CompareConfig.Compare(a, b) +} + +// Compare returns a string containing a line-by-line unified diff of the +// values in got and want according to the cfg. +func (cfg *Config) Compare(a, b interface{}) string { + diffCfg := *cfg + diffCfg.Diffable = true + return diff.Diff(cfg.Sprint(a), cfg.Sprint(b)) +} diff --git a/vendor/github.com/kylelemons/godebug/pretty/reflect.go b/vendor/github.com/kylelemons/godebug/pretty/reflect.go new file mode 100644 index 0000000000..e743a9aeaf --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/reflect.go @@ -0,0 +1,121 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pretty + +import ( + "encoding" + "fmt" + "reflect" + "sort" +) + +func isZeroVal(val reflect.Value) bool { + if !val.CanInterface() { + return false + } + z := reflect.Zero(val.Type()).Interface() + return reflect.DeepEqual(val.Interface(), z) +} + +func (c *Config) val2node(val reflect.Value) node { + // TODO(kevlar): pointer tracking? + + if !val.IsValid() { + return rawVal("nil") + } + + if val.CanInterface() { + v := val.Interface() + if formatter, ok := c.Formatter[val.Type()]; ok { + if formatter != nil { + res := reflect.ValueOf(formatter).Call([]reflect.Value{val}) + return rawVal(res[0].Interface().(string)) + } + } else { + if s, ok := v.(fmt.Stringer); ok && c.PrintStringers { + return stringVal(s.String()) + } + if t, ok := v.(encoding.TextMarshaler); ok && c.PrintTextMarshalers { + if raw, err := t.MarshalText(); err == nil { // if NOT an error + return stringVal(string(raw)) + } + } + } + } + + switch kind := val.Kind(); kind { + case reflect.Ptr, reflect.Interface: + if val.IsNil() { + return rawVal("nil") + } + return c.val2node(val.Elem()) + case reflect.String: + return stringVal(val.String()) + case reflect.Slice, reflect.Array: + n := list{} + length := val.Len() + for i := 0; i < length; i++ { + n = append(n, c.val2node(val.Index(i))) + } + return n + case reflect.Map: + n := keyvals{} + keys := val.MapKeys() + for _, key := range keys { + // TODO(kevlar): Support arbitrary type keys? + n = append(n, keyval{compactString(c.val2node(key)), c.val2node(val.MapIndex(key))}) + } + sort.Sort(n) + return n + case reflect.Struct: + n := keyvals{} + typ := val.Type() + fields := typ.NumField() + for i := 0; i < fields; i++ { + sf := typ.Field(i) + if !c.IncludeUnexported && sf.PkgPath != "" { + continue + } + field := val.Field(i) + if c.SkipZeroFields && isZeroVal(field) { + continue + } + n = append(n, keyval{sf.Name, c.val2node(field)}) + } + return n + case reflect.Bool: + if val.Bool() { + return rawVal("true") + } + return rawVal("false") + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rawVal(fmt.Sprintf("%d", val.Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return rawVal(fmt.Sprintf("%d", val.Uint())) + case reflect.Uintptr: + return rawVal(fmt.Sprintf("0x%X", val.Uint())) + case reflect.Float32, reflect.Float64: + return rawVal(fmt.Sprintf("%v", val.Float())) + case reflect.Complex64, reflect.Complex128: + return rawVal(fmt.Sprintf("%v", val.Complex())) + } + + // Fall back to the default %#v if we can + if val.CanInterface() { + return rawVal(fmt.Sprintf("%#v", val.Interface())) + } + + return rawVal(val.String()) +} diff --git a/vendor/github.com/kylelemons/godebug/pretty/structure.go b/vendor/github.com/kylelemons/godebug/pretty/structure.go new file mode 100644 index 0000000000..a2f3bb7efd --- /dev/null +++ b/vendor/github.com/kylelemons/godebug/pretty/structure.go @@ -0,0 +1,160 @@ +// Copyright 2013 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pretty + +import ( + "bytes" + "strconv" + "strings" +) + +type node interface { + WriteTo(w *bytes.Buffer, indent string, cfg *Config) +} + +func compactString(n node) string { + switch k := n.(type) { + case stringVal: + return string(k) + case rawVal: + return string(k) + } + + buf := new(bytes.Buffer) + n.WriteTo(buf, "", &Config{Compact: true}) + return buf.String() +} + +type stringVal string + +func (str stringVal) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { + w.WriteString(strconv.Quote(string(str))) +} + +type rawVal string + +func (r rawVal) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { + w.WriteString(string(r)) +} + +type keyval struct { + key string + val node +} + +type keyvals []keyval + +func (l keyvals) Len() int { return len(l) } +func (l keyvals) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l keyvals) Less(i, j int) bool { return l[i].key < l[j].key } + +func (l keyvals) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { + w.WriteByte('{') + + switch { + case cfg.Compact: + // All on one line: + for i, kv := range l { + if i > 0 { + w.WriteByte(',') + } + w.WriteString(kv.key) + w.WriteByte(':') + kv.val.WriteTo(w, indent, cfg) + } + case cfg.Diffable: + w.WriteByte('\n') + inner := indent + " " + // Each value gets its own line: + for _, kv := range l { + w.WriteString(inner) + w.WriteString(kv.key) + w.WriteString(": ") + kv.val.WriteTo(w, inner, cfg) + w.WriteString(",\n") + } + w.WriteString(indent) + default: + keyWidth := 0 + for _, kv := range l { + if kw := len(kv.key); kw > keyWidth { + keyWidth = kw + } + } + alignKey := indent + " " + alignValue := strings.Repeat(" ", keyWidth) + inner := alignKey + alignValue + " " + // First and last line shared with bracket: + for i, kv := range l { + if i > 0 { + w.WriteString(",\n") + w.WriteString(alignKey) + } + w.WriteString(kv.key) + w.WriteString(": ") + w.WriteString(alignValue[len(kv.key):]) + kv.val.WriteTo(w, inner, cfg) + } + } + + w.WriteByte('}') +} + +type list []node + +func (l list) WriteTo(w *bytes.Buffer, indent string, cfg *Config) { + if max := cfg.ShortList; max > 0 { + short := compactString(l) + if len(short) <= max { + w.WriteString(short) + return + } + } + + w.WriteByte('[') + + switch { + case cfg.Compact: + // All on one line: + for i, v := range l { + if i > 0 { + w.WriteByte(',') + } + v.WriteTo(w, indent, cfg) + } + case cfg.Diffable: + w.WriteByte('\n') + inner := indent + " " + // Each value gets its own line: + for _, v := range l { + w.WriteString(inner) + v.WriteTo(w, inner, cfg) + w.WriteString(",\n") + } + w.WriteString(indent) + default: + inner := indent + " " + // First and last line shared with bracket: + for i, v := range l { + if i > 0 { + w.WriteString(",\n") + w.WriteString(inner) + } + v.WriteTo(w, inner, cfg) + } + } + + w.WriteByte(']') +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 63be5fc7e6..e4e03487e1 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -241,10 +241,10 @@ "revisionTime": "2017-01-17T13:00:17Z" }, { - "checksumSHA1": "JUiM+lVkaSVET43yWGhTZpnfdSE=", + "checksumSHA1": "Un9C9OhF/NK5DeD4hHtY+k2riF4=", "path": "github.com/google/go-github/github", - "revision": "7a51fb928f52a196d5f31daefb8a489453ef54ff", - "revisionTime": "2017-06-04T03:01:11Z", + "revision": "b5b6d4c9c91636b0513e1c29fcfd243162331d36", + "revisionTime": "2017-06-28T21:52:13Z", "version": "master", "versionExact": "master" }, @@ -509,6 +509,18 @@ "revision": "bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d", "revisionTime": "2016-08-03T19:07:31Z" }, + { + "checksumSHA1": "0xjvz/KGzXhAFsnDRvzofE0xNzI=", + "path": "github.com/kylelemons/godebug/diff", + "revision": "a616ab194758ae0a11290d87ca46ee8c440117b0", + "revisionTime": "2017-02-24T01:00:52Z" + }, + { + "checksumSHA1": "ED154c1vFO2UMbFwdOZZ9KdfAzw=", + "path": "github.com/kylelemons/godebug/pretty", + "revision": "a616ab194758ae0a11290d87ca46ee8c440117b0", + "revisionTime": "2017-02-24T01:00:52Z" + }, { "checksumSHA1": "guxbLo8KHHBeM0rzou4OTzzpDNs=", "path": "github.com/mitchellh/copystructure", diff --git a/website/docs/r/branch_protection.html.markdown b/website/docs/r/branch_protection.html.markdown index 8feead2cdc..60a8fafd29 100644 --- a/website/docs/r/branch_protection.html.markdown +++ b/website/docs/r/branch_protection.html.markdown @@ -21,18 +21,21 @@ This resource allows you to configure branch protection for repositories in your resource "github_branch_protection" "foo_master" { repository = "foo" branch = "master" + enforce_admins = true required_status_checks { - include_admins = true strict = false contexts = ["ci/travis"] } required_pull_request_reviews { - include_admins = true + dismiss_stale_reviews = true + dismissal_users = ["foo-user"] + dismissal_teams = ["admins", "engineers"] } restrictions { + users = ["foo-user"] teams = ["engineers"] } } @@ -44,6 +47,7 @@ The following arguments are supported: * `repository` - (Required) The GitHub repository name. * `branch` - (Required) The Git branch to protect. +* `enforce_admins` - (Optional) Boolean, setting this to `true` enforces status checks for repository administrators. * `required_status_checks` - (Optional) Enforce restrictions for required status checks. See [Required Status Checks](#required-status-checks) below for details. * `required_pull_request_reviews` - (Optional) Enforce restrictions for pull request reviews. See [Required Pull Request Reviews](#required-pull-request-reviews) below for details. * `restrictions` - (Optional) Enforce restrictions for the users and teams that may push to the branch. See [Restrictions](#restrictions) below for details. @@ -52,7 +56,6 @@ The following arguments are supported: `required_status_checks` supports the following arguments: -* `include_admins`: (Optional) Enforce required status checks for repository administrators. Defaults to `false`. * `strict`: (Optional) Require branches to be up to date before merging. Defaults to `false`. * `contexts`: (Optional) The list of status checks to require in order to merge into this branch. No status checks are required by default. @@ -60,7 +63,9 @@ The following arguments are supported: `required_pull_request_reviews` supports the following arguments: -* `include_admins`: (Optional) Enforce required status checks for repository administrators. Defaults to `false`. +* `dismiss_stale_reviews`: (Optional) Dismiss approved reviews automatically when a new commit is pushed. Defaults to `false`. +* `dismissal_users`: (Optional) The list of user logins with dismissal access +* `dismissal_teams`: (Optional) The list of team slugs with dismissal access ### Restrictions