@@ -482,6 +482,64 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
482482 return "" , nil
483483}
484484
485+ // UpdateBranch moves a branch reference to the provided commit. permission check should be done before calling this function.
486+ func UpdateBranch (ctx context.Context , repo * repo_model.Repository , gitRepo * git.Repository , doer * user_model.User , branchName , newCommitID , expectedOldCommitID string , force bool ) error {
487+ branch , err := git_model .GetBranch (ctx , repo .ID , branchName )
488+ if err != nil {
489+ return err
490+ }
491+ if branch .IsDeleted {
492+ return git_model.ErrBranchNotExist {
493+ BranchName : branchName ,
494+ }
495+ }
496+
497+ if expectedOldCommitID != "" {
498+ expectedID , err := gitRepo .ConvertToGitID (expectedOldCommitID )
499+ if err != nil {
500+ return fmt .Errorf ("ConvertToGitID(old): %w" , err )
501+ }
502+ if expectedID .String () != branch .CommitID {
503+ return util .NewInvalidArgumentErrorf ("branch commit does not match [expected: %s, given: %s]" , expectedID .String (), branch .CommitID )
504+ }
505+ }
506+
507+ newID , err := gitRepo .ConvertToGitID (newCommitID )
508+ if err != nil {
509+ return fmt .Errorf ("ConvertToGitID(new): %w" , err )
510+ }
511+ newCommit , err := gitRepo .GetCommit (newID .String ())
512+ if err != nil {
513+ return err
514+ }
515+
516+ if newCommit .ID .String () == branch .CommitID {
517+ return nil
518+ }
519+
520+ isForcePush , err := newCommit .IsForcePush (branch .CommitID )
521+ if err != nil {
522+ return err
523+ }
524+ if isForcePush && ! force {
525+ return util .NewInvalidArgumentErrorf ("Force push %s need a confirm force parameter" , branchName )
526+ }
527+
528+ pushOpts := git.PushOptions {
529+ Remote : repo .RepoPath (),
530+ Branch : fmt .Sprintf ("%s:%s%s" , newCommit .ID .String (), git .BranchPrefix , branchName ),
531+ Env : repo_module .PushingEnvironment (doer , repo ),
532+ Force : isForcePush || force ,
533+ }
534+
535+ if expectedOldCommitID != "" {
536+ pushOpts .ForceWithLease = fmt .Sprintf ("%s:%s" , git .BranchPrefix + branchName , branch .CommitID )
537+ }
538+
539+ // branch protection will be checked in the pre received hook, so that we don't need any check here
540+ return gitrepo .Push (ctx , repo , repo , pushOpts )
541+ }
542+
485543var ErrBranchIsDefault = util .ErrorWrap (util .ErrPermissionDenied , "branch is default" )
486544
487545func CanDeleteBranch (ctx context.Context , repo * repo_model.Repository , branchName string , doer * user_model.User ) error {
0 commit comments