Skip to content

Commit b8597b7

Browse files
committed
Add UpdateBranch API (#35368).
This is CreateBranch for branches that already exist making it possible to reset branch to an arbitrary commit. Consistent with CreateFile/UpdateFile new branches are created by POST and existing branches updated by PUT.
1 parent 124d0d4 commit b8597b7

File tree

14 files changed

+301
-32
lines changed

14 files changed

+301
-32
lines changed

assets/go-licenses.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ require (
7777
github.com/huandu/xstrings v1.5.0
7878
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
7979
github.com/jhillyerd/enmime v1.3.0
80+
github.com/jinzhu/copier v0.4.0
8081
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
8182
github.com/klauspost/compress v1.18.0
8283
github.com/klauspost/cpuid/v2 v2.3.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,8 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ
510510
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
511511
github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw=
512512
github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs=
513+
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
514+
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
513515
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
514516
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
515517
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=

modules/structs/repo.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,30 @@ type RenameBranchRepoOption struct {
292292
Name string `json:"name" binding:"Required;GitRefName;MaxSize(100)"`
293293
}
294294

295+
// UpdateBranchRepoOption options when updating a branch reference in a repository
296+
// swagger:model
297+
type UpdateBranchRepoOption struct {
298+
// Name of the branch to update
299+
//
300+
// required: true
301+
// unique: true
302+
BranchName string `json:"new_branch_name" binding:"Required;GitRefName;MaxSize(100)"`
303+
304+
// the commit ID (SHA) for the branch that already exists to update
305+
SHA string `json:"sha" binding:"Required"`
306+
307+
// Deprecated: true
308+
// Name of the old branch to reset to
309+
//
310+
// unique: true
311+
OldBranchName string `json:"old_branch_name" binding:"GitRefName;MaxSize(100)"`
312+
313+
// Name of the old branch/tag/commit to reset to
314+
//
315+
// unique: true
316+
OldRefName string `json:"old_ref_name" binding:"GitRefName;MaxSize(100)"`
317+
}
318+
295319
// TransferRepoOption options when transfer a repository's ownership
296320
// swagger:model
297321
type TransferRepoOption struct {

routers/api/v1/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,7 @@ func Routes() *web.Router {
12421242
m.Get("/*", repo.GetBranch)
12431243
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
12441244
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
1245+
m.Put("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.UpdateBranchRepoOption{}), repo.UpdateBranch)
12451246
m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.RenameBranchRepoOption{}), repo.RenameBranch)
12461247
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
12471248
m.Group("/branch_protections", func() {

routers/api/v1/repo/branch.go

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package repo
66

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

1112
"code.gitea.io/gitea/models/db"
@@ -26,6 +27,8 @@ import (
2627
pull_service "code.gitea.io/gitea/services/pull"
2728
release_service "code.gitea.io/gitea/services/release"
2829
repo_service "code.gitea.io/gitea/services/repository"
30+
31+
"github.com/jinzhu/copier"
2932
)
3033

3134
// GetBranch get a branch of a repository
@@ -203,6 +206,62 @@ func CreateBranch(ctx *context.APIContext) {
203206
// "423":
204207
// "$ref": "#/responses/repoArchivedError"
205208

209+
optCreate := web.GetForm(ctx).(*api.CreateBranchRepoOption)
210+
opt := api.UpdateBranchRepoOption{}
211+
err := copier.Copy(&opt, optCreate)
212+
if err != nil {
213+
ctx.APIError(http.StatusInternalServerError, fmt.Sprintf("Error processing request %s.", err))
214+
return
215+
}
216+
217+
CreateUpdateRepoBranch(ctx, &opt)
218+
}
219+
220+
// UpdateBranch update (reset) a branch in a user's repository
221+
func UpdateBranch(ctx *context.APIContext) {
222+
// swagger:operation PUT /repos/{owner}/{repo}/branches repository repoUpdateBranch
223+
// ---
224+
// summary: Update a branch
225+
// consumes:
226+
// - application/json
227+
// produces:
228+
// - application/json
229+
// parameters:
230+
// - name: owner
231+
// in: path
232+
// description: owner of the repo
233+
// type: string
234+
// required: true
235+
// - name: repo
236+
// in: path
237+
// description: name of the repo
238+
// type: string
239+
// required: true
240+
// - name: body
241+
// in: body
242+
// schema:
243+
// "$ref": "#/definitions/UpdateBranchRepoOption"
244+
// responses:
245+
// "201":
246+
// "$ref": "#/responses/Branch"
247+
// "403":
248+
// description: The branch is archived or a mirror.
249+
// "404":
250+
// description: The branch does not exist.
251+
// "409":
252+
// description: The branch SHA does not match.
253+
// "423":
254+
// "$ref": "#/responses/repoArchivedError"
255+
256+
opt := web.GetForm(ctx).(*api.UpdateBranchRepoOption)
257+
258+
CreateUpdateRepoBranch(ctx, opt)
259+
}
260+
261+
func CreateUpdateRepoBranch(ctx *context.APIContext, opt *api.UpdateBranchRepoOption) {
262+
var oldCommit *git.Commit
263+
var err error
264+
206265
if ctx.Repo.Repository.IsEmpty {
207266
ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
208267
return
@@ -213,11 +272,6 @@ func CreateBranch(ctx *context.APIContext) {
213272
return
214273
}
215274

216-
opt := web.GetForm(ctx).(*api.CreateBranchRepoOption)
217-
218-
var oldCommit *git.Commit
219-
var err error
220-
221275
if len(opt.OldRefName) > 0 {
222276
oldCommit, err = ctx.Repo.GitRepo.GetCommit(opt.OldRefName)
223277
if err != nil {
@@ -243,14 +297,16 @@ func CreateBranch(ctx *context.APIContext) {
243297
}
244298
}
245299

246-
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
300+
err = repo_service.CreateUpdateBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName, opt.SHA)
247301
if err != nil {
248302
if git_model.IsErrBranchNotExist(err) {
249303
ctx.APIError(http.StatusNotFound, "The old branch does not exist")
250304
} else if release_service.IsErrTagAlreadyExists(err) {
251305
ctx.APIError(http.StatusConflict, "The branch with the same tag already exists.")
252-
} else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
306+
} else if git_model.IsErrBranchAlreadyExists(err) {
253307
ctx.APIError(http.StatusConflict, "The branch already exists.")
308+
} else if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
309+
ctx.APIError(http.StatusConflict, "The branch SHA does not match.")
254310
} else if git_model.IsErrBranchNameConflict(err) {
255311
ctx.APIError(http.StatusConflict, "The branch with the same name already exists.")
256312
} else {

routers/api/v1/swagger/options.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ type swaggerParameterBodies struct {
148148
// in:body
149149
CreateBranchRepoOption api.CreateBranchRepoOption
150150

151+
// in:body
152+
UpdateBranchRepoOption api.UpdateBranchRepoOption
153+
151154
// in:body
152155
CreateBranchProtectionOption api.CreateBranchProtectionOption
153156

routers/web/repo/branch.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,9 @@ func CreateBranch(ctx *context.Context) {
194194
}
195195
err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, target, form.NewBranchName, "")
196196
} else if ctx.Repo.RefFullName.IsBranch() {
197-
err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName)
197+
err = repo_service.CreateUpdateBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName, "")
198198
} else {
199-
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName)
199+
err = repo_service.CreateUpdateBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName, "")
200200
}
201201
if err != nil {
202202
if release_service.IsErrProtectedTagName(err) {

services/repository/branch.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ import (
3737
"xorm.io/builder"
3838
)
3939

40-
// CreateNewBranch creates a new repository branch
41-
func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldBranchName, branchName string) (err error) {
40+
// CreateUpdateBranch creates or updates a repository branch
41+
func CreateUpdateBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldBranchName, branchName, sha string) (err error) {
4242
branch, err := git_model.GetBranch(ctx, repo.ID, oldBranchName)
4343
if err != nil {
4444
return err
4545
}
4646

47-
return CreateNewBranchFromCommit(ctx, doer, repo, branch.CommitID, branchName)
47+
return CreateUpdateBranchFromCommit(ctx, doer, repo, branch.CommitID, branchName, sha)
4848
}
4949

5050
// Branch contains the branch information
@@ -373,23 +373,36 @@ func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames,
373373
})
374374
}
375375

376-
// CreateNewBranchFromCommit creates a new repository branch
377-
func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commitID, branchName string) (err error) {
376+
// CreateUpdateBranchFromCommit creates or updates a repository branch
377+
func CreateUpdateBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commitID, branchName, sha string) (err error) {
378378
err = repo.MustNotBeArchived()
379379
if err != nil {
380380
return err
381381
}
382382

383-
// Check if branch name can be used
384-
if err := checkBranchName(ctx, repo, branchName); err != nil {
385-
return err
383+
if sha != "" {
384+
_, err := git_model.GetBranch(ctx, repo.ID, branchName)
385+
if err != nil {
386+
return err
387+
}
388+
} else {
389+
// Check if branch name can be used
390+
if err := checkBranchName(ctx, repo, branchName); err != nil {
391+
return err
392+
}
386393
}
387394

388-
if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
395+
pushOpts := git.PushOptions{
389396
Remote: repo.RepoPath(),
390397
Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName),
391398
Env: repo_module.PushingEnvironment(doer, repo),
392-
}); err != nil {
399+
}
400+
401+
if sha != "" {
402+
pushOpts.ForceWithLease = fmt.Sprintf("%s:%s", git.BranchPrefix+branchName, sha)
403+
}
404+
405+
if err := git.Push(ctx, repo.RepoPath(), pushOpts); err != nil {
393406
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
394407
return err
395408
}

templates/swagger/v1_json.tmpl

Lines changed: 86 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)