diff --git a/assets/go-licenses.json b/assets/go-licenses.json index b105757683..05e19a8747 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -749,6 +749,11 @@ "path": "github.com/jhillyerd/enmime/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2012-2016 James Hillyerd, All Rights Reserved\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" }, + { + "name": "github.com/jinzhu/copier", + "path": "github.com/jinzhu/copier/License", + "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Jinzhu\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" + }, { "name": "github.com/josharian/intern", "path": "github.com/josharian/intern/license.md", diff --git a/go.mod b/go.mod index 6806e76ffc..9fe8cf02b5 100644 --- a/go.mod +++ b/go.mod @@ -77,6 +77,7 @@ require ( github.com/huandu/xstrings v1.5.0 github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 github.com/jhillyerd/enmime v1.3.0 + github.com/jinzhu/copier v0.4.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/klauspost/compress v1.18.0 github.com/klauspost/cpuid/v2 v2.3.0 diff --git a/go.sum b/go.sum index 86fe782ae7..f914b519ce 100644 --- a/go.sum +++ b/go.sum @@ -510,6 +510,8 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw= github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/modules/structs/repo.go b/modules/structs/repo.go index c1c85837fc..c5d0635622 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -292,6 +292,30 @@ type RenameBranchRepoOption struct { Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"` } +// UpdateBranchRepoOption options when updating a branch reference in a repository +// swagger:model +type UpdateBranchRepoOption struct { + // Name of the branch to update + // + // required: true + // unique: true + BranchName string `json:"new_branch_name" binding:"Required;GitRefName;MaxSize(100)"` + + // the commit ID (SHA) for the branch that already exists to update + SHA string `json:"sha" binding:"Required"` + + // Deprecated: true + // Name of the old branch to reset to + // + // unique: true + OldBranchName string `json:"old_branch_name" binding:"GitRefName;MaxSize(100)"` + + // Name of the old branch/tag/commit to reset to + // + // unique: true + OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"` +} + // TransferRepoOption options when transfer a repository's ownership // swagger:model type TransferRepoOption struct { diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 8e07685759..815459a4de 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1242,6 +1242,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.Put("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.UpdateBranchRepoOption{}), repo.UpdateBranch) m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.RenameBranchRepoOption{}), repo.RenameBranch) }, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode)) m.Group("/branch_protections", func() { diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index b9060e9cbd..6b3c3ae76a 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -6,6 +6,7 @@ package repo import ( "errors" + "fmt" "net/http" "code.gitea.io/gitea/models/db" @@ -26,6 +27,8 @@ import ( pull_service "code.gitea.io/gitea/services/pull" release_service "code.gitea.io/gitea/services/release" repo_service "code.gitea.io/gitea/services/repository" + + "github.com/jinzhu/copier" ) // GetBranch get a branch of a repository @@ -203,6 +206,62 @@ func CreateBranch(ctx *context.APIContext) { // "423": // "$ref": "#/responses/repoArchivedError" + optCreate := web.GetForm(ctx).(*api.CreateBranchRepoOption) + opt := api.UpdateBranchRepoOption{} + err := copier.Copy(&opt, optCreate) + if err != nil { + ctx.APIError(http.StatusInternalServerError, fmt.Sprintf("Error processing request %s.", err)) + return + } + + CreateUpdateRepoBranch(ctx, &opt) +} + +// UpdateBranch update (reset) a branch in a user's repository +func UpdateBranch(ctx *context.APIContext) { + // swagger:operation PUT /repos/{owner}/{repo}/branches 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: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateBranchRepoOption" + // responses: + // "201": + // "$ref": "#/responses/Branch" + // "403": + // description: The branch is archived or a mirror. + // "404": + // description: The branch does not exist. + // "409": + // description: The branch SHA does not match. + // "423": + // "$ref": "#/responses/repoArchivedError" + + opt := web.GetForm(ctx).(*api.UpdateBranchRepoOption) + + CreateUpdateRepoBranch(ctx, opt) +} + +func CreateUpdateRepoBranch(ctx *context.APIContext, opt *api.UpdateBranchRepoOption) { + var oldCommit *git.Commit + var err error + if ctx.Repo.Repository.IsEmpty { ctx.APIError(http.StatusNotFound, "Git Repository is empty.") return @@ -213,11 +272,6 @@ func CreateBranch(ctx *context.APIContext) { return } - opt := web.GetForm(ctx).(*api.CreateBranchRepoOption) - - var oldCommit *git.Commit - var err error - if len(opt.OldRefName) > 0 { oldCommit, err = ctx.Repo.GitRepo.GetCommit(opt.OldRefName) if err != nil { @@ -243,14 +297,16 @@ func CreateBranch(ctx *context.APIContext) { } } - err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName) + err = repo_service.CreateUpdateBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName, opt.SHA) if err != nil { if git_model.IsErrBranchNotExist(err) { ctx.APIError(http.StatusNotFound, "The old branch does not exist") } else if release_service.IsErrTagAlreadyExists(err) { ctx.APIError(http.StatusConflict, "The branch with the same tag already exists.") - } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { + } else if git_model.IsErrBranchAlreadyExists(err) { ctx.APIError(http.StatusConflict, "The branch already exists.") + } else if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { + ctx.APIError(http.StatusConflict, "The branch SHA does not match.") } else if git_model.IsErrBranchNameConflict(err) { ctx.APIError(http.StatusConflict, "The branch with the same name already exists.") } else { diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index b80a9c14ba..9cf84b98ff 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -148,6 +148,9 @@ type swaggerParameterBodies struct { // in:body CreateBranchRepoOption api.CreateBranchRepoOption + // in:body + UpdateBranchRepoOption api.UpdateBranchRepoOption + // in:body CreateBranchProtectionOption api.CreateBranchProtectionOption diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index f21f568231..853a928d7f 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -194,9 +194,9 @@ func CreateBranch(ctx *context.Context) { } err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, target, form.NewBranchName, "") } else if ctx.Repo.RefFullName.IsBranch() { - err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName) + err = repo_service.CreateUpdateBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName, "") } else { - err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName) + err = repo_service.CreateUpdateBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName, "") } if err != nil { if release_service.IsErrProtectedTagName(err) { diff --git a/services/repository/branch.go b/services/repository/branch.go index 0a2fd30620..9bc4e5daa5 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -37,14 +37,14 @@ import ( "xorm.io/builder" ) -// CreateNewBranch creates a new repository branch -func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldBranchName, branchName string) (err error) { +// CreateUpdateBranch creates or updates a repository branch +func CreateUpdateBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldBranchName, branchName, sha string) (err error) { branch, err := git_model.GetBranch(ctx, repo.ID, oldBranchName) if err != nil { return err } - return CreateNewBranchFromCommit(ctx, doer, repo, branch.CommitID, branchName) + return CreateUpdateBranchFromCommit(ctx, doer, repo, branch.CommitID, branchName, sha) } // Branch contains the branch information @@ -373,23 +373,36 @@ func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames, }) } -// CreateNewBranchFromCommit creates a new repository branch -func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commitID, branchName string) (err error) { +// CreateUpdateBranchFromCommit creates or updates a repository branch +func CreateUpdateBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commitID, branchName, sha string) (err error) { err = repo.MustNotBeArchived() if err != nil { return err } - // Check if branch name can be used - if err := checkBranchName(ctx, repo, branchName); err != nil { - return err + if sha != "" { + _, err := git_model.GetBranch(ctx, repo.ID, branchName) + if err != nil { + return err + } + } else { + // Check if branch name can be used + if err := checkBranchName(ctx, repo, branchName); err != nil { + return err + } } - if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{ + pushOpts := git.PushOptions{ Remote: repo.RepoPath(), Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName), Env: repo_module.PushingEnvironment(doer, repo), - }); err != nil { + } + + if sha != "" { + pushOpts.ForceWithLease = fmt.Sprintf("%s:%s", git.BranchPrefix+branchName, sha) + } + + if err := git.Push(ctx, repo.RepoPath(), pushOpts); err != nil { if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { return err } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 0cefa6795f..0d1dd4bf31 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -6654,6 +6654,59 @@ } } }, + "put": { + "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 + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/UpdateBranchRepoOption" + } + } + ], + "responses": { + "201": { + "$ref": "#/responses/Branch" + }, + "403": { + "description": "The branch is archived or a mirror." + }, + "404": { + "description": "The branch does not exist." + }, + "409": { + "description": "The branch SHA does not match." + }, + "423": { + "$ref": "#/responses/repoArchivedError" + } + } + }, "post": { "consumes": [ "application/json" @@ -28702,6 +28755,39 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "UpdateBranchRepoOption": { + "description": "UpdateBranchRepoOption options when updating a branch reference in a repository", + "type": "object", + "required": [ + "new_branch_name" + ], + "properties": { + "new_branch_name": { + "description": "Name of the branch to update", + "type": "string", + "uniqueItems": true, + "x-go-name": "BranchName" + }, + "old_branch_name": { + "description": "Deprecated: true\nName of the old branch to reset to", + "type": "string", + "uniqueItems": true, + "x-go-name": "OldBranchName" + }, + "old_ref_name": { + "description": "Name of the old branch/tag/commit to reset to", + "type": "string", + "uniqueItems": true, + "x-go-name": "OldRefName" + }, + "sha": { + "description": "the commit ID (SHA) for the branch that already exists to update", + "type": "string", + "x-go-name": "SHA" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "UpdateFileOptions": { "description": "UpdateFileOptions options for updating or creating a file\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", diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index 9dc0ddb9df..4ae3eef136 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -412,7 +412,7 @@ jobs: assert.NoError(t, err) // create a branch - err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, branch.CommitID, "test-create-branch") + err = repo_service.CreateUpdateBranchFromCommit(t.Context(), user2, repo, branch.CommitID, "test-create-branch", "") assert.NoError(t, err) run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ Title: "add workflow", @@ -530,7 +530,7 @@ jobs: // create a new branch testBranch := "test-branch" - err = repo_service.CreateNewBranch(t.Context(), user2, repo, "main", testBranch) + err = repo_service.CreateUpdateBranch(t.Context(), user2, repo, "main", testBranch, "") assert.NoError(t, err) // create Pull @@ -1509,7 +1509,7 @@ jobs: assert.NoError(t, err) // create a branch - err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, branch.CommitID, "test-action-run-name-with-variables") + err = repo_service.CreateUpdateBranchFromCommit(t.Context(), user2, repo, branch.CommitID, "test-action-run-name-with-variables", "") assert.NoError(t, err) run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ Title: user2.LoginName + " is running this workflow", @@ -1583,7 +1583,7 @@ jobs: assert.NoError(t, err) // create a branch - err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, branch.CommitID, "test-action-run-name") + err = repo_service.CreateUpdateBranchFromCommit(t.Context(), user2, repo, branch.CommitID, "test-action-run-name", "") assert.NoError(t, err) run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ Title: "run name without variables", diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 2147ef9d0d..bfef090e78 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -4,6 +4,7 @@ package integration import ( + "encoding/base64" "net/http" "net/http/httptest" "net/url" @@ -18,14 +19,14 @@ import ( "github.com/stretchr/testify/assert" ) -func testAPIGetBranch(t *testing.T, branchName string, exists bool) { - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/branches/%s", branchName). +func testAPIGetBranch(t *testing.T, user, repo, branchName string, exists bool) string { + token := getUserToken(t, user, auth_model.AccessTokenScopeReadRepository) + req := NewRequestf(t, "GET", "/api/v1/repos/"+user+"/"+repo+"/branches/%s", branchName). AddTokenAuth(token) resp := MakeRequest(t, req, NoExpectedStatus) if !exists { assert.Equal(t, http.StatusNotFound, resp.Code) - return + return "" } assert.Equal(t, http.StatusOK, resp.Code) var branch api.Branch @@ -33,6 +34,7 @@ func testAPIGetBranch(t *testing.T, branchName string, exists bool) { assert.Equal(t, branchName, branch.Name) assert.True(t, branch.UserCanPush) assert.True(t, branch.UserCanMerge) + return branch.Commit.ID } func testAPIGetBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) *api.BranchProtection { @@ -103,7 +105,7 @@ func TestAPIGetBranch(t *testing.T) { {"feature/1", true}, {"feature/1/doesnotexist", false}, } { - testAPIGetBranch(t, test.BranchName, test.Exists) + testAPIGetBranch(t, "user2", "repo1", test.BranchName, test.Exists) } } @@ -113,7 +115,8 @@ func TestAPICreateBranch(t *testing.T) { func testAPICreateBranches(t *testing.T, giteaURL *url.URL) { username := "user2" - ctx := NewAPITestContext(t, username, "my-noo-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + reponame := "my-noo-repo" + ctx := NewAPITestContext(t, username, reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) giteaURL.Path = ctx.GitPath() t.Run("CreateRepo", doAPICreateRepository(ctx, false)) @@ -164,7 +167,7 @@ func testAPICreateBranches(t *testing.T, giteaURL *url.URL) { for _, test := range testCases { session := ctx.Session t.Run(test.NewBranch, func(t *testing.T) { - testAPICreateBranch(t, session, "user2", "my-noo-repo", test.OldBranch, test.NewBranch, test.ExpectedHTTPStatus) + testAPICreateBranch(t, session, username, reponame, test.OldBranch, test.NewBranch, test.ExpectedHTTPStatus) }) } } @@ -251,6 +254,81 @@ func testAPIRenameBranch(t *testing.T, doerName, ownerName, repoName, from, to s return MakeRequest(t, req, expectedHTTPStatus) } +func TestAPIUpdateBranch(t *testing.T) { + onGiteaRun(t, testAPIUpdateBranches) +} + +func testAPIUpdateBranches(t *testing.T, giteaURL *url.URL) { + username := "user2" + reponame := "my-nuu-repo" + branchname := "new-branch" + ctx := NewAPITestContext(t, username, reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + giteaURL.Path = ctx.GitPath() + session := ctx.Session + + t.Run("CreateRepo", doAPICreateRepository(ctx, false)) + + t.Run("create branch", func(t *testing.T) { + testAPICreateBranch(t, session, username, reponame, "", branchname, http.StatusCreated) + }) + + oldCommit := "" + t.Run("get commit ID", func(t *testing.T) { + oldCommit = testAPIGetBranch(t, username, reponame, branchname, true) + }) + t.Run("advance branch", func(t *testing.T) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+username+"/"+reponame+"/contents/a new file", + &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: branchname, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("foo")), + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + assert.Equal(t, http.StatusCreated, resp.Result().StatusCode) + }) + newCommit := "" + t.Run("get new commit ID", func(t *testing.T) { + newCommit = testAPIGetBranch(t, username, reponame, branchname, true) + }) + + t.Run("fail update nonexistent branch", func(t *testing.T) { + testAPIUpdateBranch(t, session, username, reponame, oldCommit, "no-branch-here", oldCommit, http.StatusNotFound) + }) + t.Run("fail update on sha mismatch", func(t *testing.T) { + testAPIUpdateBranch(t, session, username, reponame, oldCommit, branchname, oldCommit, http.StatusConflict) + assert.Equal(t, newCommit, testAPIGetBranch(t, username, reponame, branchname, true)) + }) + t.Run("reset to old commit", func(t *testing.T) { + testAPIUpdateBranch(t, session, username, reponame, oldCommit, branchname, newCommit, http.StatusCreated) + assert.Equal(t, oldCommit, testAPIGetBranch(t, username, reponame, branchname, true)) + }) + t.Run("reset to new commit", func(t *testing.T) { + testAPIUpdateBranch(t, session, username, reponame, newCommit, branchname, oldCommit, http.StatusCreated) + assert.Equal(t, newCommit, testAPIGetBranch(t, username, reponame, branchname, true)) + }) +} + +func testAPIUpdateBranch(t testing.TB, session *TestSession, user, repo, oldBranch, newBranch, sha string, status int) bool { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + req := NewRequestWithJSON(t, "PUT", "/api/v1/repos/"+user+"/"+repo+"/branches", &api.UpdateBranchRepoOption{ + BranchName: newBranch, + OldRefName: oldBranch, + SHA: sha, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, status) + + var branch api.Branch + DecodeJSON(t, resp, &branch) + + if resp.Result().StatusCode == http.StatusCreated { + assert.Equal(t, newBranch, branch.Name) + } + + return resp.Result().StatusCode == status +} + func TestAPIBranchProtection(t *testing.T) { defer tests.PrepareTestEnv(t)() diff --git a/tests/integration/api_repo_get_contents_list_test.go b/tests/integration/api_repo_get_contents_list_test.go index 4984559f0c..fe3f835281 100644 --- a/tests/integration/api_repo_get_contents_list_test.go +++ b/tests/integration/api_repo_get_contents_list_test.go @@ -80,7 +80,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { // Make a new branch in repo1 newBranch := "test_branch" - err = repo_service.CreateNewBranch(t.Context(), user2, repo1, repo1.DefaultBranch, newBranch) + err = repo_service.CreateUpdateBranch(t.Context(), user2, repo1, repo1.DefaultBranch, newBranch, "") assert.NoError(t, err) commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch) diff --git a/tests/integration/api_repo_get_contents_test.go b/tests/integration/api_repo_get_contents_test.go index 6225c96bc6..6f272b44ff 100644 --- a/tests/integration/api_repo_get_contents_test.go +++ b/tests/integration/api_repo_get_contents_test.go @@ -85,7 +85,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { // Make a new branch in repo1 newBranch := "test_branch" - err = repo_service.CreateNewBranch(t.Context(), user2, repo1, repo1.DefaultBranch, newBranch) + err = repo_service.CreateUpdateBranch(t.Context(), user2, repo1, repo1.DefaultBranch, newBranch, "") require.NoError(t, err) commitID, err := gitRepo.GetBranchCommitID(repo1.DefaultBranch)