Skip to content

Commit b83b17e

Browse files
committed
add API endpoints to get/update/delete protected branch
1 parent d7211c5 commit b83b17e

File tree

5 files changed

+222
-14
lines changed

5 files changed

+222
-14
lines changed

modules/structs/repo_branch.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package structs
66

77
// Branch represents a repository branch
88
type Branch struct {
9-
Name string `json:"name"`
10-
Commit *PayloadCommit `json:"commit"`
9+
Name string `json:"name"`
10+
Commit *PayloadCommit `json:"commit"`
11+
Protected bool `json:"protected"`
1112
}

routers/api/v1/api.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,11 @@ func RegisterRoutes(m *macaron.Macaron) {
641641
m.Group("/branches", func() {
642642
m.Get("", repo.ListBranches)
643643
m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch)
644+
m.Group("/:branch/protection", func() {
645+
m.Combo("").Get(repo.GetProtectedBranchBy).
646+
Put(bind(auth.ProtectBranchForm{}), repo.UpdateProtectBranch).
647+
Delete(repo.DeleteProtectedBranch)
648+
}, reqToken(), reqAdmin())
644649
}, reqRepoReader(models.UnitTypeCode))
645650
m.Group("/tags", func() {
646651
m.Get("", repo.ListTags)

routers/api/v1/convert/convert.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ func ToEmail(email *models.EmailAddress) *api.Email {
2727
}
2828
}
2929

30-
// ToBranch convert a git.Commit and git.Branch to an api.Branch
31-
func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit) *api.Branch {
30+
// ToBranch convert a commit and branch to an api.Branch
31+
func ToBranch(repo *models.Repository, b *git.Branch, c *git.Commit, protected bool) *api.Branch {
3232
return &api.Branch{
33-
Name: b.Name,
34-
Commit: ToCommit(repo, c),
33+
Name: b.Name,
34+
Commit: ToCommit(repo, c),
35+
Protected: protected,
3536
}
3637
}
3738

routers/api/v1/repo/branch.go

Lines changed: 206 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
package repo
77

88
import (
9+
"net/http"
10+
"strings"
11+
12+
"code.gitea.io/gitea/models"
13+
"code.gitea.io/gitea/modules/auth"
14+
"code.gitea.io/gitea/modules/base"
915
"code.gitea.io/gitea/modules/context"
1016
"code.gitea.io/gitea/modules/git"
1117
"code.gitea.io/gitea/routers/api/v1/convert"
@@ -46,23 +52,30 @@ func GetBranch(ctx *context.APIContext) {
4652
ctx.NotFound()
4753
return
4854
}
49-
branch, err := ctx.Repo.Repository.GetBranch(ctx.Repo.BranchName)
55+
branchName := ctx.Repo.BranchName
56+
branch, err := ctx.Repo.Repository.GetBranch(branchName)
5057
if err != nil {
5158
if git.IsErrBranchNotExist(err) {
5259
ctx.NotFound(err)
5360
} else {
54-
ctx.Error(500, "GetBranch", err)
61+
ctx.Error(http.StatusInternalServerError, "GetBranch", err)
5562
}
5663
return
5764
}
5865

66+
protected, err := ctx.Repo.Repository.IsProtectedBranch(branchName, ctx.Repo.Owner)
67+
if err != nil {
68+
ctx.Error(http.StatusInternalServerError, "IsProtectedBranch", err)
69+
return
70+
}
71+
5972
c, err := branch.GetCommit()
6073
if err != nil {
61-
ctx.Error(500, "GetCommit", err)
74+
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
6275
return
6376
}
6477

65-
ctx.JSON(200, convert.ToBranch(ctx.Repo.Repository, branch, c))
78+
ctx.JSON(http.StatusOK, convert.ToBranch(ctx.Repo.Repository, branch, c, protected))
6679
}
6780

6881
// ListBranches list all the branches of a repository
@@ -88,19 +101,204 @@ func ListBranches(ctx *context.APIContext) {
88101
// "$ref": "#/responses/BranchList"
89102
branches, err := ctx.Repo.Repository.GetBranches()
90103
if err != nil {
91-
ctx.Error(500, "GetBranches", err)
104+
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
92105
return
93106
}
94107

95108
apiBranches := make([]*api.Branch, len(branches))
96109
for i := range branches {
97110
c, err := branches[i].GetCommit()
98111
if err != nil {
99-
ctx.Error(500, "GetCommit", err)
112+
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
113+
return
114+
}
115+
116+
protected, err := ctx.Repo.Repository.IsProtectedBranch(branches[i].Name, ctx.Repo.Owner)
117+
if err != nil {
118+
ctx.Error(http.StatusInternalServerError, "IsProtectedBranch", err)
119+
return
120+
}
121+
122+
apiBranches[i] = convert.ToBranch(ctx.Repo.Repository, branches[i], c, protected)
123+
}
124+
125+
ctx.JSON(http.StatusOK, &apiBranches)
126+
}
127+
128+
// GetProtectedBranchBy getting protected branch by ID/Name
129+
func GetProtectedBranchBy(ctx *context.APIContext) {
130+
// swagger:operation GET /repos/{owner}/{repo}/branches/{branch}/protection repository repoGetBranchProtection
131+
// ---
132+
// summary: Retrieve a specific branch protection from a repository
133+
// produces:
134+
// - application/json
135+
// parameters:
136+
// - name: owner
137+
// in: path
138+
// description: owner of the repo
139+
// type: string
140+
// required: true
141+
// - name: repo
142+
// in: path
143+
// description: name of the repo
144+
// type: string
145+
// required: true
146+
// - name: branch
147+
// in: path
148+
// description: branch to get
149+
// type: string
150+
// required: true
151+
// responses:
152+
// "200":
153+
// "$ref": "#/responses/Branch"
154+
protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, ctx.Params(":branch"))
155+
if err != nil {
156+
if !git.IsErrBranchNotExist(err) {
157+
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
100158
return
101159
}
102-
apiBranches[i] = convert.ToBranch(ctx.Repo.Repository, branches[i], c)
103160
}
161+
ctx.JSON(http.StatusOK, protectBranch)
162+
}
104163

105-
ctx.JSON(200, &apiBranches)
164+
// UpdateProtectBranch saves branch protection options of repository.
165+
// If ID is 0, it creates a new record. Otherwise, updates existing record.
166+
// This function also performs check if whitelist user and team's IDs have been changed
167+
// to avoid unnecessary whitelist delete and regenerate.
168+
func UpdateProtectBranch(ctx *context.APIContext, f auth.ProtectBranchForm) {
169+
// swagger:operation PUT /repos/{owner}/{repo}/branches/{branch}/protection repository repoUpdateProtectBranch
170+
// ---
171+
// summary: Update branch protection of a repository
172+
// produces:
173+
// - application/json
174+
// parameters:
175+
// - name: owner
176+
// in: path
177+
// description: owner of the repo
178+
// type: string
179+
// required: true
180+
// - name: repo
181+
// in: path
182+
// description: name of the repo
183+
// type: string
184+
// required: true
185+
// - name: branch
186+
// in: path
187+
// description: branch to update
188+
// type: string
189+
// required: true
190+
// - name: body
191+
// in: body
192+
// required: true
193+
// schema:
194+
// "$ref": "#/definitions/ProtectBranchForm"
195+
// responses:
196+
// "200":
197+
// "$ref": "#/responses/Branch"
198+
branch := ctx.Params(":branch")
199+
protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, branch)
200+
if err != nil {
201+
if !git.IsErrBranchNotExist(err) {
202+
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
203+
return
204+
}
205+
}
206+
207+
if f.Protected {
208+
if protectBranch == nil {
209+
protectBranch = &models.ProtectedBranch{
210+
RepoID: ctx.Repo.Repository.ID,
211+
BranchName: branch,
212+
}
213+
}
214+
215+
var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
216+
protectBranch.EnableWhitelist = f.EnableWhitelist
217+
if strings.TrimSpace(f.WhitelistUsers) != "" {
218+
whitelistUsers, _ = base.StringsToInt64s(strings.Split(f.WhitelistUsers, ","))
219+
}
220+
if strings.TrimSpace(f.WhitelistTeams) != "" {
221+
whitelistTeams, _ = base.StringsToInt64s(strings.Split(f.WhitelistTeams, ","))
222+
}
223+
protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist
224+
if strings.TrimSpace(f.MergeWhitelistUsers) != "" {
225+
mergeWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ","))
226+
}
227+
if strings.TrimSpace(f.MergeWhitelistTeams) != "" {
228+
mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ","))
229+
}
230+
protectBranch.RequiredApprovals = f.RequiredApprovals
231+
if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" {
232+
approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ","))
233+
}
234+
if strings.TrimSpace(f.ApprovalsWhitelistTeams) != "" {
235+
approvalsWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistTeams, ","))
236+
}
237+
if err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
238+
UserIDs: whitelistUsers,
239+
TeamIDs: whitelistTeams,
240+
MergeUserIDs: mergeWhitelistUsers,
241+
MergeTeamIDs: mergeWhitelistTeams,
242+
ApprovalsUserIDs: approvalsWhitelistUsers,
243+
ApprovalsTeamIDs: approvalsWhitelistTeams,
244+
}); err != nil {
245+
ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
246+
return
247+
}
248+
} else if protectBranch != nil {
249+
if err := ctx.Repo.Repository.DeleteProtectedBranch(protectBranch.ID); err != nil {
250+
ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
251+
return
252+
}
253+
}
254+
ctx.JSON(http.StatusOK, protectBranch)
255+
}
256+
257+
// DeleteProtectedBranch removes ProtectedBranch relation between the user and repository.
258+
func DeleteProtectedBranch(ctx *context.APIContext) {
259+
// swagger:operation DELETE /repos/{owner}/{repo}/branches/{branch}/protection repository repoDeleteProtectedBranch
260+
// ---
261+
// summary: Remove branch protection from a repository
262+
// produces:
263+
// - application/json
264+
// parameters:
265+
// - name: owner
266+
// in: path
267+
// description: owner of the repo
268+
// type: string
269+
// required: true
270+
// - name: repo
271+
// in: path
272+
// description: name of the repo
273+
// type: string
274+
// required: true
275+
// - name: branch
276+
// in: path
277+
// description: branch to remove protection
278+
// type: string
279+
// required: true
280+
// responses:
281+
// "204":
282+
// "$ref": "#/responses/empty"
283+
branchName := ctx.Params(":branch")
284+
protected, err := ctx.Repo.Repository.IsProtectedBranch(branchName, ctx.Repo.Owner)
285+
if err != nil {
286+
ctx.Error(http.StatusInternalServerError, "IsProtectedBranch", err)
287+
return
288+
}
289+
if !protected {
290+
ctx.Status(http.StatusNoContent)
291+
return
292+
}
293+
protectBranch, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, branchName)
294+
if err != nil {
295+
if !git.IsErrBranchNotExist(err) {
296+
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
297+
return
298+
}
299+
}
300+
if err = ctx.Repo.Repository.DeleteProtectedBranch(protectBranch.ID); err != nil {
301+
ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
302+
}
303+
ctx.Status(http.StatusNoContent)
106304
}

routers/api/v1/swagger/options.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ type swaggerParameterBodies struct {
8686
// in:body
8787
CreateForkOption api.CreateForkOption
8888

89+
// in:body
90+
ProtectBranchForm auth.ProtectBranchForm
91+
8992
// in:body
9093
CreateStatusOption api.CreateStatusOption
9194

0 commit comments

Comments
 (0)