Skip to content

Commit 01b1896

Browse files
kemzebsillyguodongwxiaoguang
authored
Implement update branch API (#32433)
Resolves #22526. Builds upon #23061. --------- Co-authored-by: sillyguodong <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent 1e751d8 commit 01b1896

File tree

6 files changed

+189
-0
lines changed

6 files changed

+189
-0
lines changed

modules/structs/repo.go

+10
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,16 @@ type CreateBranchRepoOption struct {
278278
OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"`
279279
}
280280

281+
// UpdateBranchRepoOption options when updating a branch in a repository
282+
// swagger:model
283+
type UpdateBranchRepoOption struct {
284+
// New branch name
285+
//
286+
// required: true
287+
// unique: true
288+
Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"`
289+
}
290+
281291
// TransferRepoOption options when transfer a repository's ownership
282292
// swagger:model
283293
type TransferRepoOption struct {

routers/api/v1/api.go

+1
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,7 @@ func Routes() *web.Router {
11951195
m.Get("/*", repo.GetBranch)
11961196
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
11971197
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
1198+
m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.UpdateBranchRepoOption{}), repo.UpdateBranch)
11981199
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
11991200
m.Group("/branch_protections", func() {
12001201
m.Get("", repo.ListBranchProtections)

routers/api/v1/repo/branch.go

+71
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,77 @@ func ListBranches(ctx *context.APIContext) {
386386
ctx.JSON(http.StatusOK, apiBranches)
387387
}
388388

389+
// UpdateBranch updates a repository's branch.
390+
func UpdateBranch(ctx *context.APIContext) {
391+
// swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoUpdateBranch
392+
// ---
393+
// summary: Update a branch
394+
// consumes:
395+
// - application/json
396+
// produces:
397+
// - application/json
398+
// parameters:
399+
// - name: owner
400+
// in: path
401+
// description: owner of the repo
402+
// type: string
403+
// required: true
404+
// - name: repo
405+
// in: path
406+
// description: name of the repo
407+
// type: string
408+
// required: true
409+
// - name: branch
410+
// in: path
411+
// description: name of the branch
412+
// type: string
413+
// required: true
414+
// - name: body
415+
// in: body
416+
// schema:
417+
// "$ref": "#/definitions/UpdateBranchRepoOption"
418+
// responses:
419+
// "204":
420+
// "$ref": "#/responses/empty"
421+
// "403":
422+
// "$ref": "#/responses/forbidden"
423+
// "404":
424+
// "$ref": "#/responses/notFound"
425+
// "422":
426+
// "$ref": "#/responses/validationError"
427+
428+
opt := web.GetForm(ctx).(*api.UpdateBranchRepoOption)
429+
430+
oldName := ctx.PathParam("*")
431+
repo := ctx.Repo.Repository
432+
433+
if repo.IsEmpty {
434+
ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
435+
return
436+
}
437+
438+
if repo.IsMirror {
439+
ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
440+
return
441+
}
442+
443+
msg, err := repo_service.RenameBranch(ctx, repo, ctx.Doer, ctx.Repo.GitRepo, oldName, opt.Name)
444+
if err != nil {
445+
ctx.Error(http.StatusInternalServerError, "RenameBranch", err)
446+
return
447+
}
448+
if msg == "target_exist" {
449+
ctx.Error(http.StatusUnprocessableEntity, "", "Cannot rename a branch using the same name or rename to a branch that already exists.")
450+
return
451+
}
452+
if msg == "from_not_exist" {
453+
ctx.Error(http.StatusNotFound, "", "Branch doesn't exist.")
454+
return
455+
}
456+
457+
ctx.Status(http.StatusNoContent)
458+
}
459+
389460
// GetBranchProtection gets a branch protection
390461
func GetBranchProtection(ctx *context.APIContext) {
391462
// swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection

routers/api/v1/swagger/options.go

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ type swaggerParameterBodies struct {
9090
// in:body
9191
EditRepoOption api.EditRepoOption
9292
// in:body
93+
UpdateBranchRepoOption api.UpdateBranchRepoOption
94+
// in:body
9395
TransferRepoOption api.TransferRepoOption
9496
// in:body
9597
CreateForkOption api.CreateForkOption

templates/swagger/v1_json.tmpl

+73
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/integration/api_branch_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package integration
55

66
import (
77
"net/http"
8+
"net/http/httptest"
89
"net/url"
910
"testing"
1011

@@ -186,6 +187,37 @@ func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBran
186187
return resp.Result().StatusCode == status
187188
}
188189

190+
func TestAPIUpdateBranch(t *testing.T) {
191+
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
192+
t.Run("UpdateBranchWithEmptyRepo", func(t *testing.T) {
193+
testAPIUpdateBranch(t, "user10", "repo6", "master", "test", http.StatusNotFound)
194+
})
195+
t.Run("UpdateBranchWithSameBranchNames", func(t *testing.T) {
196+
resp := testAPIUpdateBranch(t, "user2", "repo1", "master", "master", http.StatusUnprocessableEntity)
197+
assert.Contains(t, resp.Body.String(), "Cannot rename a branch using the same name or rename to a branch that already exists.")
198+
})
199+
t.Run("UpdateBranchThatAlreadyExists", func(t *testing.T) {
200+
resp := testAPIUpdateBranch(t, "user2", "repo1", "master", "branch2", http.StatusUnprocessableEntity)
201+
assert.Contains(t, resp.Body.String(), "Cannot rename a branch using the same name or rename to a branch that already exists.")
202+
})
203+
t.Run("UpdateBranchWithNonExistentBranch", func(t *testing.T) {
204+
resp := testAPIUpdateBranch(t, "user2", "repo1", "i-dont-exist", "new-branch-name", http.StatusNotFound)
205+
assert.Contains(t, resp.Body.String(), "Branch doesn't exist.")
206+
})
207+
t.Run("RenameBranchNormalScenario", func(t *testing.T) {
208+
testAPIUpdateBranch(t, "user2", "repo1", "branch2", "new-branch-name", http.StatusNoContent)
209+
})
210+
})
211+
}
212+
213+
func testAPIUpdateBranch(t *testing.T, ownerName, repoName, from, to string, expectedHTTPStatus int) *httptest.ResponseRecorder {
214+
token := getUserToken(t, ownerName, auth_model.AccessTokenScopeWriteRepository)
215+
req := NewRequestWithJSON(t, "PATCH", "api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from, &api.UpdateBranchRepoOption{
216+
Name: to,
217+
}).AddTokenAuth(token)
218+
return MakeRequest(t, req, expectedHTTPStatus)
219+
}
220+
189221
func TestAPIBranchProtection(t *testing.T) {
190222
defer tests.PrepareTestEnv(t)()
191223

0 commit comments

Comments
 (0)