mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-22 15:23:14 -05:00
Implement update branch API (#32433)
Resolves #22526. Builds upon #23061. --------- Co-authored-by: sillyguodong <33891828+sillyguodong@users.noreply.github.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> (cherry picked from commit 01b1896bf5eacfd7f4f64d9ebb0ad165e3e60a5c) Conflicts: routers/api/v1/api.go context conflict + s/PathParam/Params/ templates/swagger/v1_json.tmpl make generate-swagger
This commit is contained in:
parent
c3d37894aa
commit
b0d6a7f07b
6 changed files with 189 additions and 0 deletions
|
@ -290,6 +290,16 @@ type CreateBranchRepoOption struct {
|
||||||
OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"`
|
OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateBranchRepoOption options when updating a branch in a repository
|
||||||
|
// swagger:model
|
||||||
|
type UpdateBranchRepoOption struct {
|
||||||
|
// New branch name
|
||||||
|
//
|
||||||
|
// required: true
|
||||||
|
// unique: true
|
||||||
|
Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"`
|
||||||
|
}
|
||||||
|
|
||||||
// TransferRepoOption options when transfer a repository's ownership
|
// TransferRepoOption options when transfer a repository's ownership
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type TransferRepoOption struct {
|
type TransferRepoOption struct {
|
||||||
|
|
|
@ -1153,6 +1153,7 @@ func Routes() *web.Route {
|
||||||
m.Get("/*", repo.GetBranch)
|
m.Get("/*", repo.GetBranch)
|
||||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
|
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
|
||||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeGitAll, context.QuotaTargetRepo), repo.CreateBranch)
|
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), context.EnforceQuotaAPI(quota_model.LimitSubjectSizeGitAll, context.QuotaTargetRepo), repo.CreateBranch)
|
||||||
|
m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.UpdateBranchRepoOption{}), repo.UpdateBranch)
|
||||||
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
||||||
m.Group("/branch_protections", func() {
|
m.Group("/branch_protections", func() {
|
||||||
m.Get("", repo.ListBranchProtections)
|
m.Get("", repo.ListBranchProtections)
|
||||||
|
|
|
@ -393,6 +393,77 @@ func ListBranches(ctx *context.APIContext) {
|
||||||
ctx.JSON(http.StatusOK, apiBranches)
|
ctx.JSON(http.StatusOK, apiBranches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateBranch updates a repository's branch.
|
||||||
|
func UpdateBranch(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoUpdateBranch
|
||||||
|
// ---
|
||||||
|
// summary: Update 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: branch
|
||||||
|
// in: path
|
||||||
|
// description: name of the branch
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/UpdateBranchRepoOption"
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
|
||||||
|
opt := web.GetForm(ctx).(*api.UpdateBranchRepoOption)
|
||||||
|
|
||||||
|
oldName := ctx.Params("*")
|
||||||
|
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, oldName, opt.Name)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "RenameBranch", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if msg == "target_exist" {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "", "Cannot rename a branch using the same name or rename to a branch that already exists.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if msg == "from_not_exist" {
|
||||||
|
ctx.Error(http.StatusNotFound, "", "Branch doesn't exist.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
// GetBranchProtection gets a branch protection
|
// GetBranchProtection gets a branch protection
|
||||||
func GetBranchProtection(ctx *context.APIContext) {
|
func GetBranchProtection(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
|
// swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
|
||||||
|
|
|
@ -101,6 +101,8 @@ type swaggerParameterBodies struct {
|
||||||
// in:body
|
// in:body
|
||||||
EditRepoOption api.EditRepoOption
|
EditRepoOption api.EditRepoOption
|
||||||
// in:body
|
// in:body
|
||||||
|
UpdateBranchRepoOption api.UpdateBranchRepoOption
|
||||||
|
// in:body
|
||||||
TransferRepoOption api.TransferRepoOption
|
TransferRepoOption api.TransferRepoOption
|
||||||
// in:body
|
// in:body
|
||||||
CreateForkOption api.CreateForkOption
|
CreateForkOption api.CreateForkOption
|
||||||
|
|
73
templates/swagger/v1_json.tmpl
generated
73
templates/swagger/v1_json.tmpl
generated
|
@ -5806,6 +5806,63 @@
|
||||||
"$ref": "#/responses/repoArchivedError"
|
"$ref": "#/responses/repoArchivedError"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Update a branch",
|
||||||
|
"operationId": "repoUpdateBranch",
|
||||||
|
"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": "name of the branch",
|
||||||
|
"name": "branch",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/UpdateBranchRepoOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/repos/{owner}/{repo}/collaborators": {
|
"/repos/{owner}/{repo}/collaborators": {
|
||||||
|
@ -27288,6 +27345,22 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"UpdateBranchRepoOption": {
|
||||||
|
"description": "UpdateBranchRepoOption options when updating a branch in a repository",
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "New branch name",
|
||||||
|
"type": "string",
|
||||||
|
"uniqueItems": true,
|
||||||
|
"x-go-name": "Name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"UpdateFileOptions": {
|
"UpdateFileOptions": {
|
||||||
"description": "UpdateFileOptions options for updating files\nNote: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)",
|
"description": "UpdateFileOptions options for updating files\nNote: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
@ -5,6 +5,7 @@ package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -187,6 +188,37 @@ func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBran
|
||||||
return resp.Result().StatusCode == status
|
return resp.Result().StatusCode == status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIUpdateBranch(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, _ *url.URL) {
|
||||||
|
t.Run("UpdateBranchWithEmptyRepo", func(t *testing.T) {
|
||||||
|
testAPIUpdateBranch(t, "user10", "repo6", "master", "test", http.StatusNotFound)
|
||||||
|
})
|
||||||
|
t.Run("UpdateBranchWithSameBranchNames", func(t *testing.T) {
|
||||||
|
resp := testAPIUpdateBranch(t, "user2", "repo1", "master", "master", http.StatusUnprocessableEntity)
|
||||||
|
assert.Contains(t, resp.Body.String(), "Cannot rename a branch using the same name or rename to a branch that already exists.")
|
||||||
|
})
|
||||||
|
t.Run("UpdateBranchThatAlreadyExists", func(t *testing.T) {
|
||||||
|
resp := testAPIUpdateBranch(t, "user2", "repo1", "master", "branch2", http.StatusUnprocessableEntity)
|
||||||
|
assert.Contains(t, resp.Body.String(), "Cannot rename a branch using the same name or rename to a branch that already exists.")
|
||||||
|
})
|
||||||
|
t.Run("UpdateBranchWithNonExistentBranch", func(t *testing.T) {
|
||||||
|
resp := testAPIUpdateBranch(t, "user2", "repo1", "i-dont-exist", "new-branch-name", http.StatusNotFound)
|
||||||
|
assert.Contains(t, resp.Body.String(), "Branch doesn't exist.")
|
||||||
|
})
|
||||||
|
t.Run("RenameBranchNormalScenario", func(t *testing.T) {
|
||||||
|
testAPIUpdateBranch(t, "user2", "repo1", "branch2", "new-branch-name", http.StatusNoContent)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAPIUpdateBranch(t *testing.T, ownerName, repoName, from, to string, expectedHTTPStatus int) *httptest.ResponseRecorder {
|
||||||
|
token := getUserToken(t, ownerName, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
req := NewRequestWithJSON(t, "PATCH", "api/v1/repos/"+ownerName+"/"+repoName+"/branches/"+from, &api.UpdateBranchRepoOption{
|
||||||
|
Name: to,
|
||||||
|
}).AddTokenAuth(token)
|
||||||
|
return MakeRequest(t, req, expectedHTTPStatus)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIBranchProtection(t *testing.T) {
|
func TestAPIBranchProtection(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue