Skip to content

Commit 3c5a4d0

Browse files
authored
[API] Add branch delete (#11112)
* use same process as in routers/repo/branch.go/deleteBranch * make sure default branch can not be deleted * remove IsDefaultBranch from UI process - it is worth its own pull * permissions
1 parent 0ef11ff commit 3c5a4d0

File tree

4 files changed

+157
-0
lines changed

4 files changed

+157
-0
lines changed

integrations/api_branch_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ func testAPIDeleteBranchProtection(t *testing.T, branchName string, expectedHTTP
8080
session.MakeRequest(t, req, expectedHTTPStatus)
8181
}
8282

83+
func testAPIDeleteBranch(t *testing.T, branchName string, expectedHTTPStatus int) {
84+
session := loginUser(t, "user2")
85+
token := getTokenForLoggedInUser(t, session)
86+
req := NewRequestf(t, "DELETE", "/api/v1/repos/user2/repo1/branches/%s?token=%s", branchName, token)
87+
session.MakeRequest(t, req, expectedHTTPStatus)
88+
}
89+
8390
func TestAPIGetBranch(t *testing.T) {
8491
for _, test := range []struct {
8592
BranchName string
@@ -106,10 +113,17 @@ func TestAPIBranchProtection(t *testing.T) {
106113
// Can only create once
107114
testAPICreateBranchProtection(t, "master", http.StatusForbidden)
108115

116+
// Can't delete a protected branch
117+
testAPIDeleteBranch(t, "master", http.StatusForbidden)
118+
109119
testAPIGetBranchProtection(t, "master", http.StatusOK)
110120
testAPIEditBranchProtection(t, "master", &api.BranchProtection{
111121
EnablePush: true,
112122
}, http.StatusOK)
113123

114124
testAPIDeleteBranchProtection(t, "master", http.StatusNoContent)
125+
126+
// Test branch deletion
127+
testAPIDeleteBranch(t, "master", http.StatusForbidden)
128+
testAPIDeleteBranch(t, "branch2", http.StatusNoContent)
115129
}

routers/api/v1/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ func RegisterRoutes(m *macaron.Macaron) {
664664
m.Group("/branches", func() {
665665
m.Get("", repo.ListBranches)
666666
m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch)
667+
m.Delete("/*", reqRepoWriter(models.UnitTypeCode), context.RepoRefByType(context.RepoRefBranch), repo.DeleteBranch)
667668
}, reqRepoReader(models.UnitTypeCode))
668669
m.Group("/branch_protections", func() {
669670
m.Get("", repo.ListBranchProtections)

routers/api/v1/repo/branch.go

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

88
import (
9+
"fmt"
910
"net/http"
1011

1112
"code.gitea.io/gitea/models"
1213
"code.gitea.io/gitea/modules/context"
1314
"code.gitea.io/gitea/modules/convert"
1415
"code.gitea.io/gitea/modules/git"
16+
"code.gitea.io/gitea/modules/log"
17+
"code.gitea.io/gitea/modules/repofiles"
1518
repo_module "code.gitea.io/gitea/modules/repository"
1619
api "code.gitea.io/gitea/modules/structs"
1720
)
@@ -81,6 +84,104 @@ func GetBranch(ctx *context.APIContext) {
8184
ctx.JSON(http.StatusOK, br)
8285
}
8386

87+
// DeleteBranch get a branch of a repository
88+
func DeleteBranch(ctx *context.APIContext) {
89+
// swagger:operation DELETE /repos/{owner}/{repo}/branches/{branch} repository repoDeleteBranch
90+
// ---
91+
// summary: Delete a specific branch from a repository
92+
// produces:
93+
// - application/json
94+
// parameters:
95+
// - name: owner
96+
// in: path
97+
// description: owner of the repo
98+
// type: string
99+
// required: true
100+
// - name: repo
101+
// in: path
102+
// description: name of the repo
103+
// type: string
104+
// required: true
105+
// - name: branch
106+
// in: path
107+
// description: branch to delete
108+
// type: string
109+
// required: true
110+
// responses:
111+
// "204":
112+
// "$ref": "#/responses/empty"
113+
// "403":
114+
// "$ref": "#/responses/error"
115+
116+
if ctx.Repo.TreePath != "" {
117+
// if TreePath != "", then URL contained extra slashes
118+
// (i.e. "master/subbranch" instead of "master"), so branch does
119+
// not exist
120+
ctx.NotFound()
121+
return
122+
}
123+
124+
if ctx.Repo.Repository.DefaultBranch == ctx.Repo.BranchName {
125+
ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
126+
return
127+
}
128+
129+
isProtected, err := ctx.Repo.Repository.IsProtectedBranch(ctx.Repo.BranchName, ctx.User)
130+
if err != nil {
131+
ctx.InternalServerError(err)
132+
return
133+
}
134+
if isProtected {
135+
ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
136+
return
137+
}
138+
139+
branch, err := repo_module.GetBranch(ctx.Repo.Repository, ctx.Repo.BranchName)
140+
if err != nil {
141+
if git.IsErrBranchNotExist(err) {
142+
ctx.NotFound(err)
143+
} else {
144+
ctx.Error(http.StatusInternalServerError, "GetBranch", err)
145+
}
146+
return
147+
}
148+
149+
c, err := branch.GetCommit()
150+
if err != nil {
151+
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
152+
return
153+
}
154+
155+
if err := ctx.Repo.GitRepo.DeleteBranch(ctx.Repo.BranchName, git.DeleteBranchOptions{
156+
Force: true,
157+
}); err != nil {
158+
ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
159+
return
160+
}
161+
162+
// Don't return error below this
163+
if err := repofiles.PushUpdate(
164+
ctx.Repo.Repository,
165+
ctx.Repo.BranchName,
166+
repofiles.PushUpdateOptions{
167+
RefFullName: git.BranchPrefix + ctx.Repo.BranchName,
168+
OldCommitID: c.ID.String(),
169+
NewCommitID: git.EmptySHA,
170+
PusherID: ctx.User.ID,
171+
PusherName: ctx.User.Name,
172+
RepoUserName: ctx.Repo.Owner.Name,
173+
RepoName: ctx.Repo.Repository.Name,
174+
}); err != nil {
175+
log.Error("Update: %v", err)
176+
}
177+
178+
if err := ctx.Repo.Repository.AddDeletedBranch(ctx.Repo.BranchName, c.ID.String(), ctx.User.ID); err != nil {
179+
log.Warn("AddDeletedBranch: %v", err)
180+
}
181+
182+
ctx.Status(http.StatusNoContent)
183+
}
184+
84185
// ListBranches list all the branches of a repository
85186
func ListBranches(ctx *context.APIContext) {
86187
// swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches

templates/swagger/v1_json.tmpl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2269,6 +2269,47 @@
22692269
"$ref": "#/responses/Branch"
22702270
}
22712271
}
2272+
},
2273+
"delete": {
2274+
"produces": [
2275+
"application/json"
2276+
],
2277+
"tags": [
2278+
"repository"
2279+
],
2280+
"summary": "Delete a specific branch from a repository",
2281+
"operationId": "repoDeleteBranch",
2282+
"parameters": [
2283+
{
2284+
"type": "string",
2285+
"description": "owner of the repo",
2286+
"name": "owner",
2287+
"in": "path",
2288+
"required": true
2289+
},
2290+
{
2291+
"type": "string",
2292+
"description": "name of the repo",
2293+
"name": "repo",
2294+
"in": "path",
2295+
"required": true
2296+
},
2297+
{
2298+
"type": "string",
2299+
"description": "branch to delete",
2300+
"name": "branch",
2301+
"in": "path",
2302+
"required": true
2303+
}
2304+
],
2305+
"responses": {
2306+
"204": {
2307+
"$ref": "#/responses/empty"
2308+
},
2309+
"403": {
2310+
"$ref": "#/responses/error"
2311+
}
2312+
}
22722313
}
22732314
},
22742315
"/repos/{owner}/{repo}/collaborators": {

0 commit comments

Comments
 (0)