Skip to content

add API endpoints to update/delete protected branch #7093

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions modules/structs/repo_branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package structs

// 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"`
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need more details about the branch protection settings but not only a bool.

5 changes: 5 additions & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,11 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/branches", func() {
m.Get("", repo.ListBranches)
m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch)
m.Group("/:branch/protection", func() {
m.Combo("").Get(repo.GetProtectedBranchBy).
Put(bind(auth.ProtectBranchForm{}), repo.UpdateProtectBranch).
Delete(repo.DeleteProtectedBranch)
}, reqToken(), reqAdmin())
}, reqRepoReader(models.UnitTypeCode))
m.Group("/tags", func() {
m.Get("", repo.ListTags)
Expand Down
9 changes: 5 additions & 4 deletions routers/api/v1/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ 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) *api.Branch {
// ToBranch convert a commit and branch to an api.Branch
func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit, protected bool) *api.Branch {
return &api.Branch{
Name: b.Name,
Commit: ToCommit(repo, c),
Name: b.Name,
Commit: ToCommit(repo, c),
Protected: protected,
}
}

Expand Down
214 changes: 206 additions & 8 deletions routers/api/v1/repo/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
package repo

import (
"net/http"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
Expand Down Expand Up @@ -45,23 +51,30 @@ func GetBranch(ctx *context.APIContext) {
ctx.NotFound()
return
}
branch, err := ctx.Repo.Repository.GetBranch(ctx.Repo.BranchName)
branchName := ctx.Repo.BranchName
branch, err := ctx.Repo.Repository.GetBranch(branchName)
if err != nil {
if git.IsErrBranchNotExist(err) {
ctx.NotFound(err)
} else {
ctx.Error(500, "GetBranch", err)
ctx.Error(http.StatusInternalServerError, "GetBranch", err)
}
return
}

protected, err := ctx.Repo.Repository.IsProtectedBranch(branchName, ctx.Repo.Owner)
if err != nil {
ctx.Error(http.StatusInternalServerError, "IsProtectedBranch", err)
return
}

c, err := branch.GetCommit()
if err != nil {
ctx.Error(500, "GetCommit", err)
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}

ctx.JSON(200, convert.ToBranch(ctx.Repo.Repository, branch, c))
ctx.JSON(http.StatusOK, convert.ToBranch(ctx.Repo.Repository, branch, c, protected))
}

// ListBranches list all the branches of a repository
Expand All @@ -87,19 +100,204 @@ func ListBranches(ctx *context.APIContext) {
// "$ref": "#/responses/BranchList"
branches, err := ctx.Repo.Repository.GetBranches()
if err != nil {
ctx.Error(500, "GetBranches", err)
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return
}

apiBranches := make([]*api.Branch, len(branches))
for i := range branches {
c, err := branches[i].GetCommit()
if err != nil {
ctx.Error(500, "GetCommit", err)
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}

protected, err := ctx.Repo.Repository.IsProtectedBranch(branches[i].Name, ctx.Repo.Owner)
if err != nil {
ctx.Error(http.StatusInternalServerError, "IsProtectedBranch", err)
return
}

apiBranches[i] = convert.ToBranch(ctx.Repo.Repository, branches[i], c, protected)
}

ctx.JSON(http.StatusOK, &apiBranches)
}

// GetProtectedBranchBy getting protected branch by ID/Name
func GetProtectedBranchBy(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/branches/{branch}/protection repository repoGetBranchProtection
// ---
// summary: Retrieve a specific branch protection from 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
// - name: branch
// in: path
// description: branch to get
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Branch"
protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, ctx.Params(":branch"))
if err != nil {
if !git.IsErrBranchNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
return
}
Copy link
Contributor

@davidsvantesson davidsvantesson Aug 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Xorm will not give an error if there is no row in the database, but an empty object.
You need to handle:

  • If there is no protected branch, but the branch exist (create protectBranch with default values / protection:false )
  • If the branch doesn't exist. (404)

apiBranches[i] = convert.ToBranch(ctx.Repo.Repository, branches[i], c)
}
ctx.JSON(http.StatusOK, protectBranch)
}

ctx.JSON(200, &apiBranches)
// UpdateProtectBranch saves branch protection options of repository.
// If ID is 0, it creates a new record. Otherwise, updates existing record.
// This function also performs check if whitelist user and team's IDs have been changed
// to avoid unnecessary whitelist delete and regenerate.
func UpdateProtectBranch(ctx *context.APIContext, f auth.ProtectBranchForm) {
// swagger:operation PUT /repos/{owner}/{repo}/branches/{branch}/protection repository repoUpdateProtectBranch
// ---
// summary: Update branch protection of 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
// - name: branch
// in: path
// description: branch to update
// type: string
// required: true
// - name: body
// in: body
// required: true
// schema:
// "$ref": "#/definitions/ProtectBranchForm"
// responses:
// "200":
// "$ref": "#/responses/Branch"
branch := ctx.Params(":branch")
protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, branch)
if err != nil {
if !git.IsErrBranchNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
return
}
Copy link
Contributor

@davidsvantesson davidsvantesson Aug 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same problem as above with xorm.
If the protected branch does not exist, you need to check if the branch exist (or give 404)

}

if f.Protected {
if protectBranch == nil {
protectBranch = &models.ProtectedBranch{
RepoID: ctx.Repo.Repository.ID,
BranchName: branch,
}
}

var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
protectBranch.EnableWhitelist = f.EnableWhitelist
if strings.TrimSpace(f.WhitelistUsers) != "" {
whitelistUsers, _ = base.StringsToInt64s(strings.Split(f.WhitelistUsers, ","))
}
if strings.TrimSpace(f.WhitelistTeams) != "" {
whitelistTeams, _ = base.StringsToInt64s(strings.Split(f.WhitelistTeams, ","))
}
protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist
if strings.TrimSpace(f.MergeWhitelistUsers) != "" {
mergeWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ","))
}
if strings.TrimSpace(f.MergeWhitelistTeams) != "" {
mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ","))
}
protectBranch.RequiredApprovals = f.RequiredApprovals
if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" {
approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ","))
}
if strings.TrimSpace(f.ApprovalsWhitelistTeams) != "" {
approvalsWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistTeams, ","))
}
if err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
UserIDs: whitelistUsers,
TeamIDs: whitelistTeams,
MergeUserIDs: mergeWhitelistUsers,
MergeTeamIDs: mergeWhitelistTeams,
ApprovalsUserIDs: approvalsWhitelistUsers,
ApprovalsTeamIDs: approvalsWhitelistTeams,
}); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
return
}
} else if protectBranch != nil {
if err := ctx.Repo.Repository.DeleteProtectedBranch(protectBranch.ID); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
return
}
}
ctx.JSON(http.StatusOK, protectBranch)
}

// DeleteProtectedBranch removes ProtectedBranch relation between the user and repository.
func DeleteProtectedBranch(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/branches/{branch}/protection repository repoDeleteProtectedBranch
// ---
// summary: Remove branch protection from 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
// - name: branch
// in: path
// description: branch to remove protection
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
branchName := ctx.Params(":branch")
protected, err := ctx.Repo.Repository.IsProtectedBranch(branchName, ctx.Repo.Owner)
if err != nil {
ctx.Error(http.StatusInternalServerError, "IsProtectedBranch", err)
return
}
if !protected {
ctx.Status(http.StatusNoContent)
return
}
protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, branchName)
if err != nil {
if !git.IsErrBranchNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
return
}
Copy link
Contributor

@davidsvantesson davidsvantesson Aug 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same problem as above with xorm. Maybe ok to give 404 directly if protected branch doesn't exist.

}
if err = ctx.Repo.Repository.DeleteProtectedBranch(protectBranch.ID); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
}
ctx.Status(http.StatusNoContent)
}
3 changes: 3 additions & 0 deletions routers/api/v1/swagger/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ type swaggerParameterBodies struct {
// in:body
CreateForkOption api.CreateForkOption

// in:body
ProtectBranchForm auth.ProtectBranchForm

// in:body
CreateStatusOption api.CreateStatusOption

Expand Down
Loading