From 2e81813c453de40c914c5a3e526cc486fde2e684 Mon Sep 17 00:00:00 2001 From: 6543 <6543@noreply.gitea.io> Date: Thu, 21 May 2020 17:52:51 +0000 Subject: [PATCH] Add BranchProtection functions (#341) Merge branch 'master' into branch-protection Merge branch 'master' into branch-protection Merge branch 'master' into branch-protection Merge branch 'master' into branch-protection Merge branch 'master' into branch-protection fix + add TESTS first draft Add structs and placeholder Update Branch struct Co-authored-by: Lunny Xiao Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/341 Reviewed-by: Lunny Xiao Reviewed-by: techknowlogick --- gitea/repo_branch.go | 11 ++- gitea/repo_branch_protection.go | 148 ++++++++++++++++++++++++++++++++ gitea/repo_branch_test.go | 78 +++++++++++++++++ 3 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 gitea/repo_branch_protection.go diff --git a/gitea/repo_branch.go b/gitea/repo_branch.go index 313c295..084a6ce 100644 --- a/gitea/repo_branch.go +++ b/gitea/repo_branch.go @@ -46,8 +46,15 @@ type PayloadCommitVerification struct { // Branch represents a repository branch type Branch struct { - Name string `json:"name"` - Commit *PayloadCommit `json:"commit"` + Name string `json:"name"` + Commit *PayloadCommit `json:"commit"` + Protected bool `json:"protected"` + RequiredApprovals int64 `json:"required_approvals"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + UserCanPush bool `json:"user_can_push"` + UserCanMerge bool `json:"user_can_merge"` + EffectiveBranchProtectionName string `json:"effective_branch_protection_name"` } // ListRepoBranchesOptions options for listing a repository's branches diff --git a/gitea/repo_branch_protection.go b/gitea/repo_branch_protection.go new file mode 100644 index 0000000..b7ef96c --- /dev/null +++ b/gitea/repo_branch_protection.go @@ -0,0 +1,148 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" +) + +// BranchProtection represents a branch protection for a repository +type BranchProtection struct { + BranchName string `json:"branch_name"` + EnablePush bool `json:"enable_push"` + EnablePushWhitelist bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` + EnableMergeWhitelist bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals int64 `json:"required_approvals"` + EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` + BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` + DismissStaleApprovals bool `json:"dismiss_stale_approvals"` + RequireSignedCommits bool `json:"require_signed_commits"` + ProtectedFilePatterns string `json:"protected_file_patterns"` + // swagger:strfmt date-time + Created time.Time `json:"created_at"` + // swagger:strfmt date-time + Updated time.Time `json:"updated_at"` +} + +// CreateBranchProtectionOption options for creating a branch protection +type CreateBranchProtectionOption struct { + BranchName string `json:"branch_name"` + EnablePush bool `json:"enable_push"` + EnablePushWhitelist bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` + EnableMergeWhitelist bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals int64 `json:"required_approvals"` + EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` + BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` + DismissStaleApprovals bool `json:"dismiss_stale_approvals"` + RequireSignedCommits bool `json:"require_signed_commits"` + ProtectedFilePatterns string `json:"protected_file_patterns"` +} + +// EditBranchProtectionOption options for editing a branch protection +type EditBranchProtectionOption struct { + EnablePush *bool `json:"enable_push"` + EnablePushWhitelist *bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"` + EnableMergeWhitelist *bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck *bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals *int64 `json:"required_approvals"` + EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"` + BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"` + DismissStaleApprovals *bool `json:"dismiss_stale_approvals"` + RequireSignedCommits *bool `json:"require_signed_commits"` + ProtectedFilePatterns *string `json:"protected_file_patterns"` +} + +// ListBranchProtectionsOptions list branch protection options +type ListBranchProtectionsOptions struct { + ListOptions +} + +// ListBranchProtections list branch protections for a repo +func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtectionsOptions) ([]*BranchProtection, error) { + if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { + return nil, err + } + bps := make([]*BranchProtection, 0, 5) + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo)) + link.RawQuery = opt.getURLQuery().Encode() + return bps, c.getParsedResponse("GET", link.String(), jsonHeader, nil, &bps) +} + +// GetBranchProtection gets a branch protection +func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtection, error) { + if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { + return nil, err + } + bp := new(BranchProtection) + return bp, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil, bp) +} + +// CreateBranchProtection creates a branch protection for a repo +func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProtectionOption) (*BranchProtection, error) { + if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { + return nil, err + } + bp := new(BranchProtection) + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return bp, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo), jsonHeader, bytes.NewReader(body), bp) +} + +// EditBranchProtection edits a branch protection for a repo +func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchProtectionOption) (*BranchProtection, error) { + if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { + return nil, err + } + bp := new(BranchProtection) + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return bp, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), bp) +} + +// DeleteBranchProtection deletes a branch protection for a repo +func (c *Client) DeleteBranchProtection(owner, repo, name string) error { + if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { + return err + } + _, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil) + return err +} diff --git a/gitea/repo_branch_test.go b/gitea/repo_branch_test.go index f39cae0..10f3c77 100644 --- a/gitea/repo_branch_test.go +++ b/gitea/repo_branch_test.go @@ -49,6 +49,84 @@ func TestRepoBranches(t *testing.T) { assert.Nil(t, b) } +func TestRepoBranchProtection(t *testing.T) { + log.Println("== TestRepoBranchProtection ==") + c := newTestClient() + var repoName = "BranchProtection" + + repo := prepareBranchTest(t, c, repoName) + if repo == nil { + return + } + assert.NotNil(t, repo) + + // ListBranchProtections + bpl, err := c.ListBranchProtections(repo.Owner.UserName, repo.Name, ListBranchProtectionsOptions{}) + assert.NoError(t, err) + assert.Len(t, bpl, 0) + + // CreateBranchProtection + bp, err := c.CreateBranchProtection(repo.Owner.UserName, repo.Name, CreateBranchProtectionOption{ + BranchName: "master", + EnablePush: true, + EnablePushWhitelist: true, + PushWhitelistUsernames: []string{"test01"}, + EnableMergeWhitelist: true, + MergeWhitelistUsernames: []string{"test01"}, + BlockOnOutdatedBranch: true, + }) + assert.NoError(t, err) + assert.EqualValues(t, "master", bp.BranchName) + assert.EqualValues(t, false, bp.EnableStatusCheck) + assert.EqualValues(t, true, bp.EnablePush) + assert.EqualValues(t, true, bp.EnablePushWhitelist) + assert.EqualValues(t, []string{"test01"}, bp.PushWhitelistUsernames) + + bp, err = c.CreateBranchProtection(repo.Owner.UserName, repo.Name, CreateBranchProtectionOption{ + BranchName: "update", + EnablePush: false, + EnableMergeWhitelist: true, + MergeWhitelistUsernames: []string{"test01"}, + }) + assert.NoError(t, err) + assert.NotNil(t, bp) + + bpl, err = c.ListBranchProtections(repo.Owner.UserName, repo.Name, ListBranchProtectionsOptions{}) + assert.NoError(t, err) + assert.Len(t, bpl, 2) + + // GetBranchProtection + bp, err = c.GetBranchProtection(repo.Owner.UserName, repo.Name, bpl[0].BranchName) + assert.NoError(t, err) + assert.EqualValues(t, bpl[0], bp) + + optTrue := true + optFalse := false + one := int64(1) + + // EditBranchProtection + bp, err = c.EditBranchProtection(repo.Owner.UserName, repo.Name, bpl[0].BranchName, EditBranchProtectionOption{ + EnablePush: &optFalse, + EnablePushWhitelist: &optFalse, + PushWhitelistUsernames: nil, + RequiredApprovals: &one, + EnableApprovalsWhitelist: &optTrue, + ApprovalsWhitelistUsernames: []string{"test01"}, + }) + assert.NoError(t, err) + assert.NotEqual(t, bpl[0], bp) + assert.EqualValues(t, bpl[0].BranchName, bp.BranchName) + assert.EqualValues(t, bpl[0].EnableMergeWhitelist, bp.EnableMergeWhitelist) + assert.EqualValues(t, bpl[0].Created, bp.Created) + + // DeleteBranchProtection + err = c.DeleteBranchProtection(repo.Owner.UserName, repo.Name, bpl[1].BranchName) + assert.NoError(t, err) + bpl, err = c.ListBranchProtections(repo.Owner.UserName, repo.Name, ListBranchProtectionsOptions{}) + assert.NoError(t, err) + assert.Len(t, bpl, 1) +} + func prepareBranchTest(t *testing.T, c *Client, repoName string) *Repository { origRepo, err := createTestRepo(t, repoName, c) if !assert.NoError(t, err) {