Implement rename branch API

Co-authored-by: sillyguodong <33891828+sillyguodong@users.noreply.github.com>
This commit is contained in:
Kemal Zebari 2024-11-05 23:47:46 -08:00
parent 41b4ef825d
commit 1beebeb61b
6 changed files with 205 additions and 0 deletions

View File

@ -278,6 +278,16 @@ type CreateBranchRepoOption struct {
OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"`
}
// RenameBranchOption options when rename a branch in a repository
// swagger:model
type RenameBranchRepoOption struct {
// New branch name
//
// required: true
// unique: true
NewName string `json:"new_name" binding:"Required;GitRefName;MaxSize(100)"`
}
// TransferRepoOption options when transfer a repository's ownership
// swagger:model
type TransferRepoOption struct {

View File

@ -1195,6 +1195,7 @@ func Routes() *web.Router {
m.Get("/*", repo.GetBranch)
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
m.Post("/{name}/rename", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), reqRepoWriter(unit.TypeCode), bind(api.RenameBranchRepoOption{}), repo.RenameBranch)
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
m.Group("/branch_protections", func() {
m.Get("", repo.ListBranchProtections)

View File

@ -396,6 +396,95 @@ func ListBranches(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, apiBranches)
}
// RenameBranch renames a repository's branch.
func RenameBranch(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/branches/{name}/rename repository repoRenameBranch
// ---
// summary: Rename a branch
// 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: original name of the branch
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/RenameBranchRepoOption"
// responses:
// "201":
// "$ref": "#/responses/Branch"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
opt := web.GetForm(ctx).(*api.RenameBranchRepoOption)
repo := ctx.Repo.Repository
if repo.IsEmpty {
ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
return
}
if repo.IsMirror {
ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
return
}
msg, err := repo_service.RenameBranch(ctx, repo, ctx.Doer, ctx.Repo.GitRepo, ctx.PathParam("name"), opt.NewName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "RenameBranch", err)
return
}
if msg != "" {
ctx.Error(http.StatusUnprocessableEntity, "", msg)
return
}
branch, err := ctx.Repo.GitRepo.GetBranch(opt.NewName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranch", err)
return
}
commit, err := branch.GetCommit()
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branch.Name)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetFirstMatchProtectedBranchRule", err)
return
}
br, err := convert.ToBranch(ctx, repo, opt.NewName, commit, pb, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
}
ctx.JSON(http.StatusCreated, br)
}
// GetBranchProtection gets a branch protection
func GetBranchProtection(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection

View File

@ -90,6 +90,8 @@ type swaggerParameterBodies struct {
// in:body
EditRepoOption api.EditRepoOption
// in:body
RenameBranchReopOption api.RenameBranchRepoOption
// in:body
TransferRepoOption api.TransferRepoOption
// in:body
CreateForkOption api.CreateForkOption

View File

@ -4995,6 +4995,65 @@
}
}
},
"/repos/{owner}/{repo}/branches/{name}/rename": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Rename a branch",
"operationId": "repoRenameBranch",
"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": "original name of the branch",
"name": "name",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/RenameBranchRepoOption"
}
}
],
"responses": {
"201": {
"$ref": "#/responses/Branch"
},
"403": {
"$ref": "#/responses/forbidden"
},
"404": {
"$ref": "#/responses/notFound"
},
"422": {
"$ref": "#/responses/validationError"
}
}
}
},
"/repos/{owner}/{repo}/collaborators": {
"get": {
"produces": [
@ -23982,6 +24041,22 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"RenameBranchRepoOption": {
"description": "RenameBranchOption options when rename a branch in a repository",
"type": "object",
"required": [
"new_name"
],
"properties": {
"new_name": {
"description": "New branch name",
"type": "string",
"uniqueItems": true,
"x-go-name": "NewName"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"RenameUserOption": {
"description": "RenameUserOption options when renaming a user",
"type": "object",

View File

@ -185,6 +185,34 @@ func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBran
return resp.Result().StatusCode == status
}
func TestAPIRenameBranch(t *testing.T) {
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
t.Run("RenameBranchWithEmptyRepo", func(t *testing.T) {
testAPIRenameBranch(t, "user10", "repo6", "master", "test", http.StatusNotFound)
})
t.Run("RenameBranchWithSameBranchNames", func(t *testing.T) {
testAPIRenameBranch(t, "user2", "repo1", "master", "master", http.StatusUnprocessableEntity)
})
t.Run("RenameBranchWithBranchThatAlreadyExists", func(t *testing.T) {
testAPIRenameBranch(t, "user2", "repo1", "master", "branch2", http.StatusUnprocessableEntity)
})
t.Run("RenameBranchWithNonExistentBranch", func(t *testing.T) {
testAPIRenameBranch(t, "user2", "repo1", "i-dont-exist", "branch2", http.StatusUnprocessableEntity)
})
t.Run("RenameBranchNormalScenario", func(t *testing.T) {
testAPIRenameBranch(t, "user2", "repo1", "branch2", "new-branch-name", http.StatusCreated)
})
})
}
func testAPIRenameBranch(t *testing.T, ownerName, repoName, from, to string, expectedHTTPStatus int) {
token := getUserToken(t, ownerName, auth_model.AccessTokenScopeWriteRepository)
req := NewRequestWithJSON(t, "POST", "api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from+"/rename", &api.RenameBranchRepoOption{
NewName: to,
}).AddTokenAuth(token)
MakeRequest(t, req, expectedHTTPStatus)
}
func TestAPIBranchProtection(t *testing.T) {
defer tests.PrepareTestEnv(t)()