]> source.dussan.org Git - gitea.git/commitdiff
Add API branch protection endpoint (#9311)
authorDavid Svantesson <davidsvantesson@gmail.com>
Wed, 12 Feb 2020 23:19:35 +0000 (00:19 +0100)
committerGitHub <noreply@github.com>
Wed, 12 Feb 2020 23:19:35 +0000 (23:19 +0000)
* add API branch protection endpoint

* lint

* Change to use team names instead of ids.

* Status codes.

* fix

* Fix

* Add new branch protection options (BlockOnRejectedReviews, DismissStaleApprovals, RequireSignedCommits)

* Do xorm query directly

* fix xorm GetUserNamesByIDs

* Add some tests

* Improved GetTeamNamesByID

* http status created for CreateBranchProtection

* Correct status code in integration test

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
integrations/api_branch_test.go
models/org_team.go
models/user.go
modules/convert/convert.go
modules/structs/repo_branch.go
routers/api/v1/api.go
routers/api/v1/repo/branch.go
routers/api/v1/swagger/options.go
routers/api/v1/swagger/repo.go
templates/swagger/v1_json.tmpl

index 037a42deec6a374abff9ecd075d820156e6e5139..3fe7f23229d614b8d83099f7b3a79ead7a5805ec 100644 (file)
@@ -30,6 +30,54 @@ func testAPIGetBranch(t *testing.T, branchName string, exists bool) {
        assert.EqualValues(t, branchName, branch.Name)
 }
 
+func testAPIGetBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) {
+       session := loginUser(t, "user2")
+       token := getTokenForLoggedInUser(t, session)
+       req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/branch_protections/%s?token=%s", branchName, token)
+       resp := session.MakeRequest(t, req, expectedHTTPStatus)
+
+       if resp.Code == 200 {
+               var branchProtection api.BranchProtection
+               DecodeJSON(t, resp, &branchProtection)
+               assert.EqualValues(t, branchName, branchProtection.BranchName)
+       }
+}
+
+func testAPICreateBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) {
+       session := loginUser(t, "user2")
+       token := getTokenForLoggedInUser(t, session)
+       req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections?token="+token, &api.BranchProtection{
+               BranchName: branchName,
+       })
+       resp := session.MakeRequest(t, req, expectedHTTPStatus)
+
+       if resp.Code == 201 {
+               var branchProtection api.BranchProtection
+               DecodeJSON(t, resp, &branchProtection)
+               assert.EqualValues(t, branchName, branchProtection.BranchName)
+       }
+}
+
+func testAPIEditBranchProtection(t *testing.T, branchName string, body *api.BranchProtection, expectedHTTPStatus int) {
+       session := loginUser(t, "user2")
+       token := getTokenForLoggedInUser(t, session)
+       req := NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user2/repo1/branch_protections/"+branchName+"?token="+token, body)
+       resp := session.MakeRequest(t, req, expectedHTTPStatus)
+
+       if resp.Code == 200 {
+               var branchProtection api.BranchProtection
+               DecodeJSON(t, resp, &branchProtection)
+               assert.EqualValues(t, branchName, branchProtection.BranchName)
+       }
+}
+
+func testAPIDeleteBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) {
+       session := loginUser(t, "user2")
+       token := getTokenForLoggedInUser(t, session)
+       req := NewRequestf(t, "DELETE", "/api/v1/repos/user2/repo1/branch_protections/%s?token=%s", branchName, token)
+       session.MakeRequest(t, req, expectedHTTPStatus)
+}
+
 func TestAPIGetBranch(t *testing.T) {
        for _, test := range []struct {
                BranchName string
@@ -43,3 +91,23 @@ func TestAPIGetBranch(t *testing.T) {
                testAPIGetBranch(t, test.BranchName, test.Exists)
        }
 }
+
+func TestAPIBranchProtection(t *testing.T) {
+       defer prepareTestEnv(t)()
+
+       // Branch protection only on branch that exist
+       testAPICreateBranchProtection(t, "master/doesnotexist", http.StatusNotFound)
+       // Get branch protection on branch that exist but not branch protection
+       testAPIGetBranchProtection(t, "master", http.StatusNotFound)
+
+       testAPICreateBranchProtection(t, "master", http.StatusCreated)
+       // Can only create once
+       testAPICreateBranchProtection(t, "master", http.StatusForbidden)
+
+       testAPIGetBranchProtection(t, "master", http.StatusOK)
+       testAPIEditBranchProtection(t, "master", &api.BranchProtection{
+               EnablePush: true,
+       }, http.StatusOK)
+
+       testAPIDeleteBranchProtection(t, "master", http.StatusNoContent)
+}
index 214790703c2f0fcffaea3901a0526220ba104f47..f8013d12c6e9399b54d4c07b0b975f9e2639d152 100644 (file)
@@ -553,6 +553,23 @@ func GetTeam(orgID int64, name string) (*Team, error) {
        return getTeam(x, orgID, name)
 }
 
+// GetTeamIDsByNames returns a slice of team ids corresponds to names.
+func GetTeamIDsByNames(orgID int64, names []string, ignoreNonExistent bool) ([]int64, error) {
+       ids := make([]int64, 0, len(names))
+       for _, name := range names {
+               u, err := GetTeam(orgID, name)
+               if err != nil {
+                       if ignoreNonExistent {
+                               continue
+                       } else {
+                               return nil, err
+                       }
+               }
+               ids = append(ids, u.ID)
+       }
+       return ids, nil
+}
+
 // getOwnerTeam returns team by given team name and organization.
 func getOwnerTeam(e Engine, orgID int64) (*Team, error) {
        return getTeam(e, orgID, ownerTeamName)
@@ -574,6 +591,22 @@ func GetTeamByID(teamID int64) (*Team, error) {
        return getTeamByID(x, teamID)
 }
 
+// GetTeamNamesByID returns team's lower name from a list of team ids.
+func GetTeamNamesByID(teamIDs []int64) ([]string, error) {
+       if len(teamIDs) == 0 {
+               return []string{}, nil
+       }
+
+       var teamNames []string
+       err := x.Table("team").
+               Select("lower_name").
+               In("id", teamIDs).
+               Asc("name").
+               Find(&teamNames)
+
+       return teamNames, err
+}
+
 // UpdateTeam updates information of team.
 func UpdateTeam(t *Team, authChanged bool, includeAllChanged bool) (err error) {
        if len(t.Name) == 0 {
index 220a9f9a9a5fdf71ddfe5b1b1b5b06c0bb075d76..0e493dbef74fab730d2aef85a9562205d683c622 100644 (file)
@@ -1386,6 +1386,17 @@ func GetMaileableUsersByIDs(ids []int64) ([]*User, error) {
                Find(&ous)
 }
 
+// GetUserNamesByIDs returns usernames for all resolved users from a list of Ids.
+func GetUserNamesByIDs(ids []int64) ([]string, error) {
+       unames := make([]string, 0, len(ids))
+       err := x.In("id", ids).
+               Table("user").
+               Asc("name").
+               Cols("name").
+               Find(&unames)
+       return unames, err
+}
+
 // GetUsersByIDs returns all resolved users from a list of Ids.
 func GetUsersByIDs(ids []int64) ([]*User, error) {
        ous := make([]*User, 0, len(ids))
index a69b09a2b703c6cbe919861ab35d9035f1d6543f..31b46bc7eb01e7190492215484d1d3d438479b3b 100644 (file)
@@ -30,28 +30,86 @@ func ToEmail(email *models.EmailAddress) *api.Email {
 }
 
 // ToBranch convert a git.Commit and git.Branch to an api.Branch
-func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit, bp *models.ProtectedBranch, user *models.User) *api.Branch {
+func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit, bp *models.ProtectedBranch, user *models.User, isRepoAdmin bool) *api.Branch {
        if bp == nil {
                return &api.Branch{
-                       Name:                b.Name,
-                       Commit:              ToCommit(repo, c),
-                       Protected:           false,
-                       RequiredApprovals:   0,
-                       EnableStatusCheck:   false,
-                       StatusCheckContexts: []string{},
-                       UserCanPush:         true,
-                       UserCanMerge:        true,
+                       Name:                          b.Name,
+                       Commit:                        ToCommit(repo, c),
+                       Protected:                     false,
+                       RequiredApprovals:             0,
+                       EnableStatusCheck:             false,
+                       StatusCheckContexts:           []string{},
+                       UserCanPush:                   true,
+                       UserCanMerge:                  true,
+                       EffectiveBranchProtectionName: "",
                }
        }
+       branchProtectionName := ""
+       if isRepoAdmin {
+               branchProtectionName = bp.BranchName
+       }
+
        return &api.Branch{
-               Name:                b.Name,
-               Commit:              ToCommit(repo, c),
-               Protected:           true,
-               RequiredApprovals:   bp.RequiredApprovals,
-               EnableStatusCheck:   bp.EnableStatusCheck,
-               StatusCheckContexts: bp.StatusCheckContexts,
-               UserCanPush:         bp.CanUserPush(user.ID),
-               UserCanMerge:        bp.IsUserMergeWhitelisted(user.ID),
+               Name:                          b.Name,
+               Commit:                        ToCommit(repo, c),
+               Protected:                     true,
+               RequiredApprovals:             bp.RequiredApprovals,
+               EnableStatusCheck:             bp.EnableStatusCheck,
+               StatusCheckContexts:           bp.StatusCheckContexts,
+               UserCanPush:                   bp.CanUserPush(user.ID),
+               UserCanMerge:                  bp.IsUserMergeWhitelisted(user.ID),
+               EffectiveBranchProtectionName: branchProtectionName,
+       }
+}
+
+// ToBranchProtection convert a ProtectedBranch to api.BranchProtection
+func ToBranchProtection(bp *models.ProtectedBranch) *api.BranchProtection {
+       pushWhitelistUsernames, err := models.GetUserNamesByIDs(bp.WhitelistUserIDs)
+       if err != nil {
+               log.Error("GetUserNamesByIDs (WhitelistUserIDs): %v", err)
+       }
+       mergeWhitelistUsernames, err := models.GetUserNamesByIDs(bp.MergeWhitelistUserIDs)
+       if err != nil {
+               log.Error("GetUserNamesByIDs (MergeWhitelistUserIDs): %v", err)
+       }
+       approvalsWhitelistUsernames, err := models.GetUserNamesByIDs(bp.ApprovalsWhitelistUserIDs)
+       if err != nil {
+               log.Error("GetUserNamesByIDs (ApprovalsWhitelistUserIDs): %v", err)
+       }
+       pushWhitelistTeams, err := models.GetTeamNamesByID(bp.WhitelistTeamIDs)
+       if err != nil {
+               log.Error("GetTeamNamesByID (WhitelistTeamIDs): %v", err)
+       }
+       mergeWhitelistTeams, err := models.GetTeamNamesByID(bp.MergeWhitelistTeamIDs)
+       if err != nil {
+               log.Error("GetTeamNamesByID (MergeWhitelistTeamIDs): %v", err)
+       }
+       approvalsWhitelistTeams, err := models.GetTeamNamesByID(bp.ApprovalsWhitelistTeamIDs)
+       if err != nil {
+               log.Error("GetTeamNamesByID (ApprovalsWhitelistTeamIDs): %v", err)
+       }
+
+       return &api.BranchProtection{
+               BranchName:                  bp.BranchName,
+               EnablePush:                  bp.CanPush,
+               EnablePushWhitelist:         bp.EnableWhitelist,
+               PushWhitelistUsernames:      pushWhitelistUsernames,
+               PushWhitelistTeams:          pushWhitelistTeams,
+               PushWhitelistDeployKeys:     bp.WhitelistDeployKeys,
+               EnableMergeWhitelist:        bp.EnableMergeWhitelist,
+               MergeWhitelistUsernames:     mergeWhitelistUsernames,
+               MergeWhitelistTeams:         mergeWhitelistTeams,
+               EnableStatusCheck:           bp.EnableStatusCheck,
+               StatusCheckContexts:         bp.StatusCheckContexts,
+               RequiredApprovals:           bp.RequiredApprovals,
+               EnableApprovalsWhitelist:    bp.EnableApprovalsWhitelist,
+               ApprovalsWhitelistUsernames: approvalsWhitelistUsernames,
+               ApprovalsWhitelistTeams:     approvalsWhitelistTeams,
+               BlockOnRejectedReviews:      bp.BlockOnRejectedReviews,
+               DismissStaleApprovals:       bp.DismissStaleApprovals,
+               RequireSignedCommits:        bp.RequireSignedCommits,
+               Created:                     bp.CreatedUnix.AsTime(),
+               Updated:                     bp.UpdatedUnix.AsTime(),
        }
 }
 
index 42bb7638938b25854274a7f8f99f423bd9839ba8..f8c92905488919538f06e2e9a2e8e99fcc3e0ca8 100644 (file)
@@ -4,14 +4,88 @@
 
 package structs
 
+import (
+       "time"
+)
+
 // Branch represents a repository branch
 type Branch struct {
-       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"`
+       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"`
+}
+
+// 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"`
+       DismissStaleApprovals       bool     `json:"dismiss_stale_approvals"`
+       RequireSignedCommits        bool     `json:"require_signed_commits"`
+       // 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"`
+       DismissStaleApprovals       bool     `json:"dismiss_stale_approvals"`
+       RequireSignedCommits        bool     `json:"require_signed_commits"`
+}
+
+// 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"`
+       DismissStaleApprovals       *bool    `json:"dismiss_stale_approvals"`
+       RequireSignedCommits        *bool    `json:"require_signed_commits"`
 }
index 0a352f6e46f612235acea9c7a99200dad12fb067..0ddf57b743e54a17af4b9b3f93e8954defb272b4 100644 (file)
@@ -656,6 +656,15 @@ func RegisterRoutes(m *macaron.Macaron) {
                                        m.Get("", repo.ListBranches)
                                        m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch)
                                }, reqRepoReader(models.UnitTypeCode))
+                               m.Group("/branch_protections", func() {
+                                       m.Get("", repo.ListBranchProtections)
+                                       m.Post("", bind(api.CreateBranchProtectionOption{}), repo.CreateBranchProtection)
+                                       m.Group("/:name", func() {
+                                               m.Get("", repo.GetBranchProtection)
+                                               m.Patch("", bind(api.EditBranchProtectionOption{}), repo.EditBranchProtection)
+                                               m.Delete("", repo.DeleteBranchProtection)
+                                       })
+                               }, reqToken(), reqAdmin())
                                m.Group("/tags", func() {
                                        m.Get("", repo.ListTags)
                                }, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(true))
index 8f8ca158771eba15b7fffa30af33f71cd11feecb..fccfc2bfe1aa12dd9a6821272246f3fcd79d20fa 100644 (file)
@@ -8,6 +8,7 @@ package repo
 import (
        "net/http"
 
+       "code.gitea.io/gitea/models"
        "code.gitea.io/gitea/modules/context"
        "code.gitea.io/gitea/modules/convert"
        "code.gitea.io/gitea/modules/git"
@@ -71,7 +72,7 @@ func GetBranch(ctx *context.APIContext) {
                return
        }
 
-       ctx.JSON(http.StatusOK, convert.ToBranch(ctx.Repo.Repository, branch, c, branchProtection, ctx.User))
+       ctx.JSON(http.StatusOK, convert.ToBranch(ctx.Repo.Repository, branch, c, branchProtection, ctx.User, ctx.Repo.IsAdmin()))
 }
 
 // ListBranches list all the branches of a repository
@@ -114,8 +115,509 @@ func ListBranches(ctx *context.APIContext) {
                        ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
                        return
                }
-               apiBranches[i] = convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.User)
+               apiBranches[i] = convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.User, ctx.Repo.IsAdmin())
        }
 
        ctx.JSON(http.StatusOK, &apiBranches)
 }
+
+// GetBranchProtection gets a branch protection
+func GetBranchProtection(ctx *context.APIContext) {
+       // swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
+       // ---
+       // summary: Get a specific branch protection for the repository
+       // produces:
+       // - application/json
+       // parameters:
+       // - name: owner
+       //   in: path
+       //   description: owner of the repo
+       //   type: string
+       //   required: true
+       // - name: repo
+       //   in: path
+       //   description: name of the repo
+       //   type: string
+       //   required: true
+       // - name: name
+       //   in: path
+       //   description: name of protected branch
+       //   type: string
+       //   required: true
+       // responses:
+       //   "200":
+       //     "$ref": "#/responses/BranchProtection"
+       //   "404":
+       //     "$ref": "#/responses/notFound"
+
+       repo := ctx.Repo.Repository
+       bpName := ctx.Params(":name")
+       bp, err := models.GetProtectedBranchBy(repo.ID, bpName)
+       if err != nil {
+               ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
+               return
+       }
+       if bp == nil || bp.RepoID != repo.ID {
+               ctx.NotFound()
+               return
+       }
+
+       ctx.JSON(http.StatusOK, convert.ToBranchProtection(bp))
+}
+
+// ListBranchProtections list branch protections for a repo
+func ListBranchProtections(ctx *context.APIContext) {
+       // swagger:operation GET /repos/{owner}/{repo}/branch_protections repository repoListBranchProtection
+       // ---
+       // summary: List branch protections for a repository
+       // produces:
+       // - application/json
+       // parameters:
+       // - name: owner
+       //   in: path
+       //   description: owner of the repo
+       //   type: string
+       //   required: true
+       // - name: repo
+       //   in: path
+       //   description: name of the repo
+       //   type: string
+       //   required: true
+       // responses:
+       //   "200":
+       //     "$ref": "#/responses/BranchProtectionList"
+
+       repo := ctx.Repo.Repository
+       bps, err := repo.GetProtectedBranches()
+       if err != nil {
+               ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err)
+               return
+       }
+       apiBps := make([]*api.BranchProtection, len(bps))
+       for i := range bps {
+               apiBps[i] = convert.ToBranchProtection(bps[i])
+       }
+
+       ctx.JSON(http.StatusOK, apiBps)
+}
+
+// CreateBranchProtection creates a branch protection for a repo
+func CreateBranchProtection(ctx *context.APIContext, form api.CreateBranchProtectionOption) {
+       // swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection
+       // ---
+       // summary: Create a branch protections for a repository
+       // consumes:
+       // - application/json
+       // produces:
+       // - application/json
+       // parameters:
+       // - name: owner
+       //   in: path
+       //   description: owner of the repo
+       //   type: string
+       //   required: true
+       // - name: repo
+       //   in: path
+       //   description: name of the repo
+       //   type: string
+       //   required: true
+       // - name: body
+       //   in: body
+       //   schema:
+       //     "$ref": "#/definitions/CreateBranchProtectionOption"
+       // responses:
+       //   "201":
+       //     "$ref": "#/responses/BranchProtection"
+       //   "403":
+       //     "$ref": "#/responses/forbidden"
+       //   "404":
+       //     "$ref": "#/responses/notFound"
+       //   "422":
+       //     "$ref": "#/responses/validationError"
+
+       repo := ctx.Repo.Repository
+
+       // Currently protection must match an actual branch
+       if !git.IsBranchExist(ctx.Repo.Repository.RepoPath(), form.BranchName) {
+               ctx.NotFound()
+               return
+       }
+
+       protectBranch, err := models.GetProtectedBranchBy(repo.ID, form.BranchName)
+       if err != nil {
+               ctx.Error(http.StatusInternalServerError, "GetProtectBranchOfRepoByName", err)
+               return
+       } else if protectBranch != nil {
+               ctx.Error(http.StatusForbidden, "Create branch protection", "Branch protection already exist")
+               return
+       }
+
+       var requiredApprovals int64
+       if form.RequiredApprovals > 0 {
+               requiredApprovals = form.RequiredApprovals
+       }
+
+       whitelistUsers, err := models.GetUserIDsByNames(form.PushWhitelistUsernames, false)
+       if err != nil {
+               if models.IsErrUserNotExist(err) {
+                       ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+                       return
+               }
+               ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+               return
+       }
+       mergeWhitelistUsers, err := models.GetUserIDsByNames(form.MergeWhitelistUsernames, false)
+       if err != nil {
+               if models.IsErrUserNotExist(err) {
+                       ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+                       return
+               }
+               ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+               return
+       }
+       approvalsWhitelistUsers, err := models.GetUserIDsByNames(form.ApprovalsWhitelistUsernames, false)
+       if err != nil {
+               if models.IsErrUserNotExist(err) {
+                       ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+                       return
+               }
+               ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+               return
+       }
+       var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
+       if repo.Owner.IsOrganization() {
+               whitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false)
+               if err != nil {
+                       if models.IsErrTeamNotExist(err) {
+                               ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+                               return
+                       }
+                       ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+                       return
+               }
+               mergeWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false)
+               if err != nil {
+                       if models.IsErrTeamNotExist(err) {
+                               ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+                               return
+                       }
+                       ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+                       return
+               }
+               approvalsWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false)
+               if err != nil {
+                       if models.IsErrTeamNotExist(err) {
+                               ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+                               return
+                       }
+                       ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+                       return
+               }
+       }
+
+       protectBranch = &models.ProtectedBranch{
+               RepoID:                   ctx.Repo.Repository.ID,
+               BranchName:               form.BranchName,
+               CanPush:                  form.EnablePush,
+               EnableWhitelist:          form.EnablePush && form.EnablePushWhitelist,
+               EnableMergeWhitelist:     form.EnableMergeWhitelist,
+               WhitelistDeployKeys:      form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys,
+               EnableStatusCheck:        form.EnableStatusCheck,
+               StatusCheckContexts:      form.StatusCheckContexts,
+               EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
+               RequiredApprovals:        requiredApprovals,
+               BlockOnRejectedReviews:   form.BlockOnRejectedReviews,
+               DismissStaleApprovals:    form.DismissStaleApprovals,
+               RequireSignedCommits:     form.RequireSignedCommits,
+       }
+
+       err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
+               UserIDs:          whitelistUsers,
+               TeamIDs:          whitelistTeams,
+               MergeUserIDs:     mergeWhitelistUsers,
+               MergeTeamIDs:     mergeWhitelistTeams,
+               ApprovalsUserIDs: approvalsWhitelistUsers,
+               ApprovalsTeamIDs: approvalsWhitelistTeams,
+       })
+       if err != nil {
+               ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
+               return
+       }
+
+       // Reload from db to get all whitelists
+       bp, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, form.BranchName)
+       if err != nil {
+               ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
+               return
+       }
+       if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
+               ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
+               return
+       }
+
+       ctx.JSON(http.StatusCreated, convert.ToBranchProtection(bp))
+
+}
+
+// EditBranchProtection edits a branch protection for a repo
+func EditBranchProtection(ctx *context.APIContext, form api.EditBranchProtectionOption) {
+       // swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection
+       // ---
+       // summary: Edit a branch protections for a repository. Only fields that are set will be changed
+       // consumes:
+       // - application/json
+       // produces:
+       // - application/json
+       // parameters:
+       // - name: owner
+       //   in: path
+       //   description: owner of the repo
+       //   type: string
+       //   required: true
+       // - name: repo
+       //   in: path
+       //   description: name of the repo
+       //   type: string
+       //   required: true
+       // - name: name
+       //   in: path
+       //   description: name of protected branch
+       //   type: string
+       //   required: true
+       // - name: body
+       //   in: body
+       //   schema:
+       //     "$ref": "#/definitions/EditBranchProtectionOption"
+       // responses:
+       //   "200":
+       //     "$ref": "#/responses/BranchProtection"
+       //   "404":
+       //     "$ref": "#/responses/notFound"
+       //   "422":
+       //     "$ref": "#/responses/validationError"
+
+       repo := ctx.Repo.Repository
+       bpName := ctx.Params(":name")
+       protectBranch, err := models.GetProtectedBranchBy(repo.ID, bpName)
+       if err != nil {
+               ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
+               return
+       }
+       if protectBranch == nil || protectBranch.RepoID != repo.ID {
+               ctx.NotFound()
+               return
+       }
+
+       if form.EnablePush != nil {
+               if !*form.EnablePush {
+                       protectBranch.CanPush = false
+                       protectBranch.EnableWhitelist = false
+                       protectBranch.WhitelistDeployKeys = false
+               } else {
+                       protectBranch.CanPush = true
+                       if form.EnablePushWhitelist != nil {
+                               if !*form.EnablePushWhitelist {
+                                       protectBranch.EnableWhitelist = false
+                                       protectBranch.WhitelistDeployKeys = false
+                               } else {
+                                       protectBranch.EnableWhitelist = true
+                                       if form.PushWhitelistDeployKeys != nil {
+                                               protectBranch.WhitelistDeployKeys = *form.PushWhitelistDeployKeys
+                                       }
+                               }
+                       }
+               }
+       }
+
+       if form.EnableMergeWhitelist != nil {
+               protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
+       }
+
+       if form.EnableStatusCheck != nil {
+               protectBranch.EnableStatusCheck = *form.EnableStatusCheck
+       }
+       if protectBranch.EnableStatusCheck {
+               protectBranch.StatusCheckContexts = form.StatusCheckContexts
+       }
+
+       if form.RequiredApprovals != nil && *form.RequiredApprovals >= 0 {
+               protectBranch.RequiredApprovals = *form.RequiredApprovals
+       }
+
+       if form.EnableApprovalsWhitelist != nil {
+               protectBranch.EnableApprovalsWhitelist = *form.EnableApprovalsWhitelist
+       }
+
+       if form.BlockOnRejectedReviews != nil {
+               protectBranch.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
+       }
+
+       if form.DismissStaleApprovals != nil {
+               protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
+       }
+
+       if form.RequireSignedCommits != nil {
+               protectBranch.RequireSignedCommits = *form.RequireSignedCommits
+       }
+
+       var whitelistUsers []int64
+       if form.PushWhitelistUsernames != nil {
+               whitelistUsers, err = models.GetUserIDsByNames(form.PushWhitelistUsernames, false)
+               if err != nil {
+                       if models.IsErrUserNotExist(err) {
+                               ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+                               return
+                       }
+                       ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+                       return
+               }
+       } else {
+               whitelistUsers = protectBranch.WhitelistUserIDs
+       }
+       var mergeWhitelistUsers []int64
+       if form.MergeWhitelistUsernames != nil {
+               mergeWhitelistUsers, err = models.GetUserIDsByNames(form.MergeWhitelistUsernames, false)
+               if err != nil {
+                       if models.IsErrUserNotExist(err) {
+                               ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+                               return
+                       }
+                       ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+                       return
+               }
+       } else {
+               mergeWhitelistUsers = protectBranch.MergeWhitelistUserIDs
+       }
+       var approvalsWhitelistUsers []int64
+       if form.ApprovalsWhitelistUsernames != nil {
+               approvalsWhitelistUsers, err = models.GetUserIDsByNames(form.ApprovalsWhitelistUsernames, false)
+               if err != nil {
+                       if models.IsErrUserNotExist(err) {
+                               ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
+                               return
+                       }
+                       ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
+                       return
+               }
+       } else {
+               approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs
+       }
+
+       var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
+       if repo.Owner.IsOrganization() {
+               if form.PushWhitelistTeams != nil {
+                       whitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false)
+                       if err != nil {
+                               if models.IsErrTeamNotExist(err) {
+                                       ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+                                       return
+                               }
+                               ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+                               return
+                       }
+               } else {
+                       whitelistTeams = protectBranch.WhitelistTeamIDs
+               }
+               if form.MergeWhitelistTeams != nil {
+                       mergeWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false)
+                       if err != nil {
+                               if models.IsErrTeamNotExist(err) {
+                                       ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+                                       return
+                               }
+                               ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+                               return
+                       }
+               } else {
+                       mergeWhitelistTeams = protectBranch.MergeWhitelistTeamIDs
+               }
+               if form.ApprovalsWhitelistTeams != nil {
+                       approvalsWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false)
+                       if err != nil {
+                               if models.IsErrTeamNotExist(err) {
+                                       ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
+                                       return
+                               }
+                               ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
+                               return
+                       }
+               } else {
+                       approvalsWhitelistTeams = protectBranch.ApprovalsWhitelistTeamIDs
+               }
+       }
+
+       err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
+               UserIDs:          whitelistUsers,
+               TeamIDs:          whitelistTeams,
+               MergeUserIDs:     mergeWhitelistUsers,
+               MergeTeamIDs:     mergeWhitelistTeams,
+               ApprovalsUserIDs: approvalsWhitelistUsers,
+               ApprovalsTeamIDs: approvalsWhitelistTeams,
+       })
+       if err != nil {
+               ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
+               return
+       }
+
+       // Reload from db to ensure get all whitelists
+       bp, err := models.GetProtectedBranchBy(repo.ID, bpName)
+       if err != nil {
+               ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
+               return
+       }
+       if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
+               ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
+               return
+       }
+
+       ctx.JSON(http.StatusOK, convert.ToBranchProtection(bp))
+}
+
+// DeleteBranchProtection deletes a branch protection for a repo
+func DeleteBranchProtection(ctx *context.APIContext) {
+       // swagger:operation DELETE /repos/{owner}/{repo}/branch_protections/{name} repository repoDeleteBranchProtection
+       // ---
+       // summary: Delete a specific branch protection for the repository
+       // produces:
+       // - application/json
+       // parameters:
+       // - name: owner
+       //   in: path
+       //   description: owner of the repo
+       //   type: string
+       //   required: true
+       // - name: repo
+       //   in: path
+       //   description: name of the repo
+       //   type: string
+       //   required: true
+       // - name: name
+       //   in: path
+       //   description: name of protected branch
+       //   type: string
+       //   required: true
+       // responses:
+       //   "204":
+       //     "$ref": "#/responses/empty"
+       //   "404":
+       //     "$ref": "#/responses/notFound"
+
+       repo := ctx.Repo.Repository
+       bpName := ctx.Params(":name")
+       bp, err := models.GetProtectedBranchBy(repo.ID, bpName)
+       if err != nil {
+               ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
+               return
+       }
+       if bp == nil || bp.RepoID != repo.ID {
+               ctx.NotFound()
+               return
+       }
+
+       if err := ctx.Repo.Repository.DeleteProtectedBranch(bp.ID); err != nil {
+               ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
+               return
+       }
+
+       ctx.Status(http.StatusNoContent)
+}
index ab697811d0510d74b98addfc1322cf2c990cdd62..679b4aa70855dce8821ec91577e184a6e4ef16c9 100644 (file)
@@ -128,4 +128,10 @@ type swaggerParameterBodies struct {
 
        // in:body
        EditReactionOption api.EditReactionOption
+
+       // in:body
+       CreateBranchProtectionOption api.CreateBranchProtectionOption
+
+       // in:body
+       EditBranchProtectionOption api.EditBranchProtectionOption
 }
index 4ac5c6d2d50b540d8ef5ac6e85c0bbdec57fbda7..2a657f312236a8d630f092cbbf76bf04520bad5c 100644 (file)
@@ -36,6 +36,20 @@ type swaggerResponseBranchList struct {
        Body []api.Branch `json:"body"`
 }
 
+// BranchProtection
+// swagger:response BranchProtection
+type swaggerResponseBranchProtection struct {
+       // in:body
+       Body api.BranchProtection `json:"body"`
+}
+
+// BranchProtectionList
+// swagger:response BranchProtectionList
+type swaggerResponseBranchProtectionList struct {
+       // in:body
+       Body []api.BranchProtection `json:"body"`
+}
+
 // TagList
 // swagger:response TagList
 type swaggerResponseTagList struct {
index e6a51899289fbaa345b0a349619b72bdc8679391..b52145a0a9b6f431e1c543287f1e5a66f58648a4 100644 (file)
         }
       }
     },
+    "/repos/{owner}/{repo}/branch_protections": {
+      "get": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "repository"
+        ],
+        "summary": "List branch protections for a repository",
+        "operationId": "repoListBranchProtection",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "owner of the repo",
+            "name": "owner",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of the repo",
+            "name": "repo",
+            "in": "path",
+            "required": true
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/BranchProtectionList"
+          }
+        }
+      },
+      "post": {
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "repository"
+        ],
+        "summary": "Create a branch protections for a repository",
+        "operationId": "repoCreateBranchProtection",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "owner of the repo",
+            "name": "owner",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of the repo",
+            "name": "repo",
+            "in": "path",
+            "required": true
+          },
+          {
+            "name": "body",
+            "in": "body",
+            "schema": {
+              "$ref": "#/definitions/CreateBranchProtectionOption"
+            }
+          }
+        ],
+        "responses": {
+          "201": {
+            "$ref": "#/responses/BranchProtection"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          },
+          "404": {
+            "$ref": "#/responses/notFound"
+          },
+          "422": {
+            "$ref": "#/responses/validationError"
+          }
+        }
+      }
+    },
+    "/repos/{owner}/{repo}/branch_protections/{name}": {
+      "get": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "repository"
+        ],
+        "summary": "Get a specific branch protection for the repository",
+        "operationId": "repoGetBranchProtection",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "owner of the repo",
+            "name": "owner",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of the repo",
+            "name": "repo",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of protected branch",
+            "name": "name",
+            "in": "path",
+            "required": true
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/BranchProtection"
+          },
+          "404": {
+            "$ref": "#/responses/notFound"
+          }
+        }
+      },
+      "delete": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "repository"
+        ],
+        "summary": "Delete a specific branch protection for the repository",
+        "operationId": "repoDeleteBranchProtection",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "owner of the repo",
+            "name": "owner",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of the repo",
+            "name": "repo",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of protected branch",
+            "name": "name",
+            "in": "path",
+            "required": true
+          }
+        ],
+        "responses": {
+          "204": {
+            "$ref": "#/responses/empty"
+          },
+          "404": {
+            "$ref": "#/responses/notFound"
+          }
+        }
+      },
+      "patch": {
+        "consumes": [
+          "application/json"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "repository"
+        ],
+        "summary": "Edit a branch protections for a repository. Only fields that are set will be changed",
+        "operationId": "repoEditBranchProtection",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "owner of the repo",
+            "name": "owner",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of the repo",
+            "name": "repo",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of protected branch",
+            "name": "name",
+            "in": "path",
+            "required": true
+          },
+          {
+            "name": "body",
+            "in": "body",
+            "schema": {
+              "$ref": "#/definitions/EditBranchProtectionOption"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/BranchProtection"
+          },
+          "404": {
+            "$ref": "#/responses/notFound"
+          },
+          "422": {
+            "$ref": "#/responses/validationError"
+          }
+        }
+      }
+    },
     "/repos/{owner}/{repo}/branches": {
       "get": {
         "produces": [
         "commit": {
           "$ref": "#/definitions/PayloadCommit"
         },
+        "effective_branch_protection_name": {
+          "type": "string",
+          "x-go-name": "EffectiveBranchProtectionName"
+        },
         "enable_status_check": {
           "type": "boolean",
           "x-go-name": "EnableStatusCheck"
       },
       "x-go-package": "code.gitea.io/gitea/modules/structs"
     },
+    "BranchProtection": {
+      "description": "BranchProtection represents a branch protection for a repository",
+      "type": "object",
+      "properties": {
+        "approvals_whitelist_teams": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "ApprovalsWhitelistTeams"
+        },
+        "approvals_whitelist_username": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "ApprovalsWhitelistUsernames"
+        },
+        "block_on_rejected_reviews": {
+          "type": "boolean",
+          "x-go-name": "BlockOnRejectedReviews"
+        },
+        "branch_name": {
+          "type": "string",
+          "x-go-name": "BranchName"
+        },
+        "created_at": {
+          "type": "string",
+          "format": "date-time",
+          "x-go-name": "Created"
+        },
+        "dismiss_stale_approvals": {
+          "type": "boolean",
+          "x-go-name": "DismissStaleApprovals"
+        },
+        "enable_approvals_whitelist": {
+          "type": "boolean",
+          "x-go-name": "EnableApprovalsWhitelist"
+        },
+        "enable_merge_whitelist": {
+          "type": "boolean",
+          "x-go-name": "EnableMergeWhitelist"
+        },
+        "enable_push": {
+          "type": "boolean",
+          "x-go-name": "EnablePush"
+        },
+        "enable_push_whitelist": {
+          "type": "boolean",
+          "x-go-name": "EnablePushWhitelist"
+        },
+        "enable_status_check": {
+          "type": "boolean",
+          "x-go-name": "EnableStatusCheck"
+        },
+        "merge_whitelist_teams": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "MergeWhitelistTeams"
+        },
+        "merge_whitelist_usernames": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "MergeWhitelistUsernames"
+        },
+        "push_whitelist_deploy_keys": {
+          "type": "boolean",
+          "x-go-name": "PushWhitelistDeployKeys"
+        },
+        "push_whitelist_teams": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "PushWhitelistTeams"
+        },
+        "push_whitelist_usernames": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "PushWhitelistUsernames"
+        },
+        "require_signed_commits": {
+          "type": "boolean",
+          "x-go-name": "RequireSignedCommits"
+        },
+        "required_approvals": {
+          "type": "integer",
+          "format": "int64",
+          "x-go-name": "RequiredApprovals"
+        },
+        "status_check_contexts": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "StatusCheckContexts"
+        },
+        "updated_at": {
+          "type": "string",
+          "format": "date-time",
+          "x-go-name": "Updated"
+        }
+      },
+      "x-go-package": "code.gitea.io/gitea/modules/structs"
+    },
     "Comment": {
       "description": "Comment represents a comment on a commit or issue",
       "type": "object",
       },
       "x-go-package": "code.gitea.io/gitea/modules/structs"
     },
+    "CreateBranchProtectionOption": {
+      "description": "CreateBranchProtectionOption options for creating a branch protection",
+      "type": "object",
+      "properties": {
+        "approvals_whitelist_teams": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "ApprovalsWhitelistTeams"
+        },
+        "approvals_whitelist_username": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "ApprovalsWhitelistUsernames"
+        },
+        "block_on_rejected_reviews": {
+          "type": "boolean",
+          "x-go-name": "BlockOnRejectedReviews"
+        },
+        "branch_name": {
+          "type": "string",
+          "x-go-name": "BranchName"
+        },
+        "dismiss_stale_approvals": {
+          "type": "boolean",
+          "x-go-name": "DismissStaleApprovals"
+        },
+        "enable_approvals_whitelist": {
+          "type": "boolean",
+          "x-go-name": "EnableApprovalsWhitelist"
+        },
+        "enable_merge_whitelist": {
+          "type": "boolean",
+          "x-go-name": "EnableMergeWhitelist"
+        },
+        "enable_push": {
+          "type": "boolean",
+          "x-go-name": "EnablePush"
+        },
+        "enable_push_whitelist": {
+          "type": "boolean",
+          "x-go-name": "EnablePushWhitelist"
+        },
+        "enable_status_check": {
+          "type": "boolean",
+          "x-go-name": "EnableStatusCheck"
+        },
+        "merge_whitelist_teams": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "MergeWhitelistTeams"
+        },
+        "merge_whitelist_usernames": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "MergeWhitelistUsernames"
+        },
+        "push_whitelist_deploy_keys": {
+          "type": "boolean",
+          "x-go-name": "PushWhitelistDeployKeys"
+        },
+        "push_whitelist_teams": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "PushWhitelistTeams"
+        },
+        "push_whitelist_usernames": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "PushWhitelistUsernames"
+        },
+        "require_signed_commits": {
+          "type": "boolean",
+          "x-go-name": "RequireSignedCommits"
+        },
+        "required_approvals": {
+          "type": "integer",
+          "format": "int64",
+          "x-go-name": "RequiredApprovals"
+        },
+        "status_check_contexts": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "StatusCheckContexts"
+        }
+      },
+      "x-go-package": "code.gitea.io/gitea/modules/structs"
+    },
     "CreateEmailOption": {
       "description": "CreateEmailOption options when creating email addresses",
       "type": "object",
       },
       "x-go-package": "code.gitea.io/gitea/modules/structs"
     },
+    "EditBranchProtectionOption": {
+      "description": "EditBranchProtectionOption options for editing a branch protection",
+      "type": "object",
+      "properties": {
+        "approvals_whitelist_teams": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "ApprovalsWhitelistTeams"
+        },
+        "approvals_whitelist_username": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "ApprovalsWhitelistUsernames"
+        },
+        "block_on_rejected_reviews": {
+          "type": "boolean",
+          "x-go-name": "BlockOnRejectedReviews"
+        },
+        "dismiss_stale_approvals": {
+          "type": "boolean",
+          "x-go-name": "DismissStaleApprovals"
+        },
+        "enable_approvals_whitelist": {
+          "type": "boolean",
+          "x-go-name": "EnableApprovalsWhitelist"
+        },
+        "enable_merge_whitelist": {
+          "type": "boolean",
+          "x-go-name": "EnableMergeWhitelist"
+        },
+        "enable_push": {
+          "type": "boolean",
+          "x-go-name": "EnablePush"
+        },
+        "enable_push_whitelist": {
+          "type": "boolean",
+          "x-go-name": "EnablePushWhitelist"
+        },
+        "enable_status_check": {
+          "type": "boolean",
+          "x-go-name": "EnableStatusCheck"
+        },
+        "merge_whitelist_teams": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "MergeWhitelistTeams"
+        },
+        "merge_whitelist_usernames": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "MergeWhitelistUsernames"
+        },
+        "push_whitelist_deploy_keys": {
+          "type": "boolean",
+          "x-go-name": "PushWhitelistDeployKeys"
+        },
+        "push_whitelist_teams": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "PushWhitelistTeams"
+        },
+        "push_whitelist_usernames": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "PushWhitelistUsernames"
+        },
+        "require_signed_commits": {
+          "type": "boolean",
+          "x-go-name": "RequireSignedCommits"
+        },
+        "required_approvals": {
+          "type": "integer",
+          "format": "int64",
+          "x-go-name": "RequiredApprovals"
+        },
+        "status_check_contexts": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "StatusCheckContexts"
+        }
+      },
+      "x-go-package": "code.gitea.io/gitea/modules/structs"
+    },
     "EditDeadlineOption": {
       "description": "EditDeadlineOption options for creating a deadline",
       "type": "object",
         }
       }
     },
+    "BranchProtection": {
+      "description": "BranchProtection",
+      "schema": {
+        "$ref": "#/definitions/BranchProtection"
+      }
+    },
+    "BranchProtectionList": {
+      "description": "BranchProtectionList",
+      "schema": {
+        "type": "array",
+        "items": {
+          "$ref": "#/definitions/BranchProtection"
+        }
+      }
+    },
     "Comment": {
       "description": "Comment",
       "schema": {
     "parameterBodies": {
       "description": "parameterBodies",
       "schema": {
-        "$ref": "#/definitions/EditReactionOption"
+        "$ref": "#/definitions/EditBranchProtectionOption"
       }
     },
     "redirect": {