code.gitea.io/gitea@v1.22.3/services/repository/branch.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package repository
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"code.gitea.io/gitea/models"
    13  	actions_model "code.gitea.io/gitea/models/actions"
    14  	"code.gitea.io/gitea/models/db"
    15  	git_model "code.gitea.io/gitea/models/git"
    16  	issues_model "code.gitea.io/gitea/models/issues"
    17  	repo_model "code.gitea.io/gitea/models/repo"
    18  	user_model "code.gitea.io/gitea/models/user"
    19  	"code.gitea.io/gitea/modules/cache"
    20  	"code.gitea.io/gitea/modules/git"
    21  	"code.gitea.io/gitea/modules/gitrepo"
    22  	"code.gitea.io/gitea/modules/graceful"
    23  	"code.gitea.io/gitea/modules/json"
    24  	"code.gitea.io/gitea/modules/log"
    25  	"code.gitea.io/gitea/modules/optional"
    26  	"code.gitea.io/gitea/modules/queue"
    27  	repo_module "code.gitea.io/gitea/modules/repository"
    28  	"code.gitea.io/gitea/modules/timeutil"
    29  	"code.gitea.io/gitea/modules/util"
    30  	webhook_module "code.gitea.io/gitea/modules/webhook"
    31  	notify_service "code.gitea.io/gitea/services/notify"
    32  	files_service "code.gitea.io/gitea/services/repository/files"
    33  
    34  	"xorm.io/builder"
    35  )
    36  
    37  // CreateNewBranch creates a new repository branch
    38  func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, oldBranchName, branchName string) (err error) {
    39  	branch, err := git_model.GetBranch(ctx, repo.ID, oldBranchName)
    40  	if err != nil {
    41  		return err
    42  	}
    43  
    44  	return CreateNewBranchFromCommit(ctx, doer, repo, gitRepo, branch.CommitID, branchName)
    45  }
    46  
    47  // Branch contains the branch information
    48  type Branch struct {
    49  	DBBranch          *git_model.Branch
    50  	IsProtected       bool
    51  	IsIncluded        bool
    52  	CommitsAhead      int
    53  	CommitsBehind     int
    54  	LatestPullRequest *issues_model.PullRequest
    55  	MergeMovedOn      bool
    56  }
    57  
    58  // LoadBranches loads branches from the repository limited by page & pageSize.
    59  func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch optional.Option[bool], keyword string, page, pageSize int) (*Branch, []*Branch, int64, error) {
    60  	defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch)
    61  	if err != nil {
    62  		return nil, nil, 0, err
    63  	}
    64  
    65  	branchOpts := git_model.FindBranchOptions{
    66  		RepoID:          repo.ID,
    67  		IsDeletedBranch: isDeletedBranch,
    68  		ListOptions: db.ListOptions{
    69  			Page:     page,
    70  			PageSize: pageSize,
    71  		},
    72  		Keyword:            keyword,
    73  		ExcludeBranchNames: []string{repo.DefaultBranch},
    74  	}
    75  
    76  	dbBranches, totalNumOfBranches, err := db.FindAndCount[git_model.Branch](ctx, branchOpts)
    77  	if err != nil {
    78  		return nil, nil, 0, err
    79  	}
    80  
    81  	if err := git_model.BranchList(dbBranches).LoadDeletedBy(ctx); err != nil {
    82  		return nil, nil, 0, err
    83  	}
    84  	if err := git_model.BranchList(dbBranches).LoadPusher(ctx); err != nil {
    85  		return nil, nil, 0, err
    86  	}
    87  
    88  	rules, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
    89  	if err != nil {
    90  		return nil, nil, 0, err
    91  	}
    92  
    93  	repoIDToRepo := map[int64]*repo_model.Repository{}
    94  	repoIDToRepo[repo.ID] = repo
    95  
    96  	repoIDToGitRepo := map[int64]*git.Repository{}
    97  	repoIDToGitRepo[repo.ID] = gitRepo
    98  
    99  	branches := make([]*Branch, 0, len(dbBranches))
   100  	for i := range dbBranches {
   101  		branch, err := loadOneBranch(ctx, repo, dbBranches[i], &rules, repoIDToRepo, repoIDToGitRepo)
   102  		if err != nil {
   103  			return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
   104  		}
   105  		branches = append(branches, branch)
   106  	}
   107  
   108  	// Always add the default branch
   109  	log.Debug("loadOneBranch: load default: '%s'", defaultDBBranch.Name)
   110  	defaultBranch, err := loadOneBranch(ctx, repo, defaultDBBranch, &rules, repoIDToRepo, repoIDToGitRepo)
   111  	if err != nil {
   112  		return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
   113  	}
   114  	return defaultBranch, branches, totalNumOfBranches, nil
   115  }
   116  
   117  func getDivergenceCacheKey(repoID int64, branchName string) string {
   118  	return fmt.Sprintf("%d-%s", repoID, branchName)
   119  }
   120  
   121  // getDivergenceFromCache gets the divergence from cache
   122  func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject, bool) {
   123  	data, ok := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName))
   124  	res := git.DivergeObject{
   125  		Ahead:  -1,
   126  		Behind: -1,
   127  	}
   128  	if !ok || data == "" {
   129  		return &res, false
   130  	}
   131  	if err := json.Unmarshal(util.UnsafeStringToBytes(data), &res); err != nil {
   132  		log.Error("json.UnMarshal failed: %v", err)
   133  		return &res, false
   134  	}
   135  	return &res, true
   136  }
   137  
   138  func putDivergenceFromCache(repoID int64, branchName string, divergence *git.DivergeObject) error {
   139  	bs, err := json.Marshal(divergence)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	return cache.GetCache().Put(getDivergenceCacheKey(repoID, branchName), util.UnsafeBytesToString(bs), 30*24*60*60)
   144  }
   145  
   146  func DelDivergenceFromCache(repoID int64, branchName string) error {
   147  	return cache.GetCache().Delete(getDivergenceCacheKey(repoID, branchName))
   148  }
   149  
   150  // DelRepoDivergenceFromCache deletes all divergence caches of a repository
   151  func DelRepoDivergenceFromCache(ctx context.Context, repoID int64) error {
   152  	dbBranches, err := db.Find[git_model.Branch](ctx, git_model.FindBranchOptions{
   153  		RepoID:      repoID,
   154  		ListOptions: db.ListOptionsAll,
   155  	})
   156  	if err != nil {
   157  		return err
   158  	}
   159  	for i := range dbBranches {
   160  		if err := DelDivergenceFromCache(repoID, dbBranches[i].Name); err != nil {
   161  			log.Error("DelDivergenceFromCache: %v", err)
   162  		}
   163  	}
   164  	return nil
   165  }
   166  
   167  func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *git_model.Branch, protectedBranches *git_model.ProtectedBranchRules,
   168  	repoIDToRepo map[int64]*repo_model.Repository,
   169  	repoIDToGitRepo map[int64]*git.Repository,
   170  ) (*Branch, error) {
   171  	log.Trace("loadOneBranch: '%s'", dbBranch.Name)
   172  
   173  	branchName := dbBranch.Name
   174  	p := protectedBranches.GetFirstMatched(branchName)
   175  	isProtected := p != nil
   176  
   177  	var divergence *git.DivergeObject
   178  
   179  	// it's not default branch
   180  	if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted {
   181  		var cached bool
   182  		divergence, cached = getDivergenceFromCache(repo.ID, dbBranch.Name)
   183  		if !cached {
   184  			var err error
   185  			divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName)
   186  			if err != nil {
   187  				log.Error("CountDivergingCommits: %v", err)
   188  			} else {
   189  				if err = putDivergenceFromCache(repo.ID, dbBranch.Name, divergence); err != nil {
   190  					log.Error("putDivergenceFromCache: %v", err)
   191  				}
   192  			}
   193  		}
   194  	}
   195  
   196  	if divergence == nil {
   197  		// tolerate the error that we cannot get divergence
   198  		divergence = &git.DivergeObject{Ahead: -1, Behind: -1}
   199  	}
   200  
   201  	pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx, repo.ID, branchName)
   202  	if err != nil {
   203  		return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err)
   204  	}
   205  	headCommit := dbBranch.CommitID
   206  
   207  	mergeMovedOn := false
   208  	if pr != nil {
   209  		pr.HeadRepo = repo
   210  		if err := pr.LoadIssue(ctx); err != nil {
   211  			return nil, fmt.Errorf("LoadIssue: %v", err)
   212  		}
   213  		if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
   214  			pr.BaseRepo = repo
   215  		} else if err := pr.LoadBaseRepo(ctx); err != nil {
   216  			return nil, fmt.Errorf("LoadBaseRepo: %v", err)
   217  		} else {
   218  			repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
   219  		}
   220  		pr.Issue.Repo = pr.BaseRepo
   221  
   222  		if pr.HasMerged {
   223  			baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
   224  			if !ok {
   225  				baseGitRepo, err = gitrepo.OpenRepository(ctx, pr.BaseRepo)
   226  				if err != nil {
   227  					return nil, fmt.Errorf("OpenRepository: %v", err)
   228  				}
   229  				defer baseGitRepo.Close()
   230  				repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
   231  			}
   232  			pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
   233  			if err != nil && !git.IsErrNotExist(err) {
   234  				return nil, fmt.Errorf("GetBranchCommitID: %v", err)
   235  			}
   236  			if err == nil && headCommit != pullCommit {
   237  				// the head has moved on from the merge - we shouldn't delete
   238  				mergeMovedOn = true
   239  			}
   240  		}
   241  	}
   242  
   243  	isIncluded := divergence.Ahead == 0 && repo.DefaultBranch != branchName
   244  	return &Branch{
   245  		DBBranch:          dbBranch,
   246  		IsProtected:       isProtected,
   247  		IsIncluded:        isIncluded,
   248  		CommitsAhead:      divergence.Ahead,
   249  		CommitsBehind:     divergence.Behind,
   250  		LatestPullRequest: pr,
   251  		MergeMovedOn:      mergeMovedOn,
   252  	}, nil
   253  }
   254  
   255  // checkBranchName validates branch name with existing repository branches
   256  func checkBranchName(ctx context.Context, repo *repo_model.Repository, name string) error {
   257  	_, err := gitrepo.WalkReferences(ctx, repo, func(_, refName string) error {
   258  		branchRefName := strings.TrimPrefix(refName, git.BranchPrefix)
   259  		switch {
   260  		case branchRefName == name:
   261  			return git_model.ErrBranchAlreadyExists{
   262  				BranchName: name,
   263  			}
   264  		// If branchRefName like a/b but we want to create a branch named a then we have a conflict
   265  		case strings.HasPrefix(branchRefName, name+"/"):
   266  			return git_model.ErrBranchNameConflict{
   267  				BranchName: branchRefName,
   268  			}
   269  			// Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict
   270  		case strings.HasPrefix(name, branchRefName+"/"):
   271  			return git_model.ErrBranchNameConflict{
   272  				BranchName: branchRefName,
   273  			}
   274  		case refName == git.TagPrefix+name:
   275  			return models.ErrTagAlreadyExists{
   276  				TagName: name,
   277  			}
   278  		}
   279  		return nil
   280  	})
   281  
   282  	return err
   283  }
   284  
   285  // SyncBranchesToDB sync the branch information in the database.
   286  // It will check whether the branches of the repository have never been synced before.
   287  // If so, it will sync all branches of the repository.
   288  // Otherwise, it will sync the branches that need to be updated.
   289  func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames, commitIDs []string, getCommit func(commitID string) (*git.Commit, error)) error {
   290  	// Some designs that make the code look strange but are made for performance optimization purposes:
   291  	// 1. Sync branches in a batch to reduce the number of DB queries.
   292  	// 2. Lazy load commit information since it may be not necessary.
   293  	// 3. Exit early if synced all branches of git repo when there's no branch in DB.
   294  	// 4. Check the branches in DB if they are already synced.
   295  	//
   296  	// If the user pushes many branches at once, the Git hook will call the internal API in batches, rather than all at once.
   297  	// See https://github.com/go-gitea/gitea/blob/cb52b17f92e2d2293f7c003649743464492bca48/cmd/hook.go#L27
   298  	// For the first batch, it will hit optimization 3.
   299  	// For other batches, it will hit optimization 4.
   300  
   301  	if len(branchNames) != len(commitIDs) {
   302  		return fmt.Errorf("branchNames and commitIDs length not match")
   303  	}
   304  
   305  	return db.WithTx(ctx, func(ctx context.Context) error {
   306  		branches, err := git_model.GetBranches(ctx, repoID, branchNames)
   307  		if err != nil {
   308  			return fmt.Errorf("git_model.GetBranches: %v", err)
   309  		}
   310  
   311  		if len(branches) == 0 {
   312  			// if user haven't visit UI but directly push to a branch after upgrading from 1.20 -> 1.21,
   313  			// we cannot simply insert the branch but need to check we have branches or not
   314  			hasBranch, err := db.Exist[git_model.Branch](ctx, git_model.FindBranchOptions{
   315  				RepoID:          repoID,
   316  				IsDeletedBranch: optional.Some(false),
   317  			}.ToConds())
   318  			if err != nil {
   319  				return err
   320  			}
   321  			if !hasBranch {
   322  				if _, err = repo_module.SyncRepoBranches(ctx, repoID, pusherID); err != nil {
   323  					return fmt.Errorf("repo_module.SyncRepoBranches %d failed: %v", repoID, err)
   324  				}
   325  				return nil
   326  			}
   327  		}
   328  
   329  		branchMap := make(map[string]*git_model.Branch, len(branches))
   330  		for _, branch := range branches {
   331  			branchMap[branch.Name] = branch
   332  		}
   333  
   334  		newBranches := make([]*git_model.Branch, 0, len(branchNames))
   335  
   336  		for i, branchName := range branchNames {
   337  			commitID := commitIDs[i]
   338  			branch, exist := branchMap[branchName]
   339  			if exist && branch.CommitID == commitID && !branch.IsDeleted {
   340  				continue
   341  			}
   342  
   343  			commit, err := getCommit(commitID)
   344  			if err != nil {
   345  				return fmt.Errorf("get commit of %s failed: %v", branchName, err)
   346  			}
   347  
   348  			if exist {
   349  				if _, err := git_model.UpdateBranch(ctx, repoID, pusherID, branchName, commit); err != nil {
   350  					return fmt.Errorf("git_model.UpdateBranch %d:%s failed: %v", repoID, branchName, err)
   351  				}
   352  				continue
   353  			}
   354  
   355  			// if database have branches but not this branch, it means this is a new branch
   356  			newBranches = append(newBranches, &git_model.Branch{
   357  				RepoID:        repoID,
   358  				Name:          branchName,
   359  				CommitID:      commit.ID.String(),
   360  				CommitMessage: commit.Summary(),
   361  				PusherID:      pusherID,
   362  				CommitTime:    timeutil.TimeStamp(commit.Committer.When.Unix()),
   363  			})
   364  		}
   365  
   366  		if len(newBranches) > 0 {
   367  			return db.Insert(ctx, newBranches)
   368  		}
   369  		return nil
   370  	})
   371  }
   372  
   373  // CreateNewBranchFromCommit creates a new repository branch
   374  func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, commitID, branchName string) (err error) {
   375  	err = repo.MustNotBeArchived()
   376  	if err != nil {
   377  		return err
   378  	}
   379  
   380  	// Check if branch name can be used
   381  	if err := checkBranchName(ctx, repo, branchName); err != nil {
   382  		return err
   383  	}
   384  
   385  	if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
   386  		Remote: repo.RepoPath(),
   387  		Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName),
   388  		Env:    repo_module.PushingEnvironment(doer, repo),
   389  	}); err != nil {
   390  		if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
   391  			return err
   392  		}
   393  		return fmt.Errorf("push: %w", err)
   394  	}
   395  	return nil
   396  }
   397  
   398  // RenameBranch rename a branch
   399  func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, gitRepo *git.Repository, from, to string) (string, error) {
   400  	err := repo.MustNotBeArchived()
   401  	if err != nil {
   402  		return "", err
   403  	}
   404  
   405  	if from == to {
   406  		return "target_exist", nil
   407  	}
   408  
   409  	if gitRepo.IsBranchExist(to) {
   410  		return "target_exist", nil
   411  	}
   412  
   413  	if !gitRepo.IsBranchExist(from) {
   414  		return "from_not_exist", nil
   415  	}
   416  
   417  	if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error {
   418  		err2 := gitRepo.RenameBranch(from, to)
   419  		if err2 != nil {
   420  			return err2
   421  		}
   422  
   423  		if isDefault {
   424  			// if default branch changed, we need to delete all schedules and cron jobs
   425  			if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
   426  				log.Error("DeleteCronTaskByRepo: %v", err)
   427  			}
   428  			// cancel running cron jobs of this repository and delete old schedules
   429  			if err := actions_model.CancelPreviousJobs(
   430  				ctx,
   431  				repo.ID,
   432  				from,
   433  				"",
   434  				webhook_module.HookEventSchedule,
   435  			); err != nil {
   436  				log.Error("CancelPreviousJobs: %v", err)
   437  			}
   438  
   439  			err2 = gitrepo.SetDefaultBranch(ctx, repo, to)
   440  			if err2 != nil {
   441  				return err2
   442  			}
   443  		}
   444  
   445  		return nil
   446  	}); err != nil {
   447  		return "", err
   448  	}
   449  	refNameTo := git.RefNameFromBranch(to)
   450  	refID, err := gitRepo.GetRefCommitID(refNameTo.String())
   451  	if err != nil {
   452  		return "", err
   453  	}
   454  
   455  	notify_service.DeleteRef(ctx, doer, repo, git.RefNameFromBranch(from))
   456  	notify_service.CreateRef(ctx, doer, repo, refNameTo, refID)
   457  
   458  	return "", nil
   459  }
   460  
   461  // enmuerates all branch related errors
   462  var (
   463  	ErrBranchIsDefault = errors.New("branch is default")
   464  )
   465  
   466  // DeleteBranch delete branch
   467  func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) error {
   468  	err := repo.MustNotBeArchived()
   469  	if err != nil {
   470  		return err
   471  	}
   472  
   473  	if branchName == repo.DefaultBranch {
   474  		return ErrBranchIsDefault
   475  	}
   476  
   477  	isProtected, err := git_model.IsBranchProtected(ctx, repo.ID, branchName)
   478  	if err != nil {
   479  		return err
   480  	}
   481  	if isProtected {
   482  		return git_model.ErrBranchIsProtected
   483  	}
   484  
   485  	rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName)
   486  	if err != nil && !git_model.IsErrBranchNotExist(err) {
   487  		return fmt.Errorf("GetBranch: %vc", err)
   488  	}
   489  
   490  	// database branch record not exist or it's a deleted branch
   491  	notExist := git_model.IsErrBranchNotExist(err) || rawBranch.IsDeleted
   492  
   493  	commit, err := gitRepo.GetBranchCommit(branchName)
   494  	if err != nil {
   495  		return err
   496  	}
   497  
   498  	if err := db.WithTx(ctx, func(ctx context.Context) error {
   499  		if !notExist {
   500  			if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
   501  				return err
   502  			}
   503  		}
   504  
   505  		return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
   506  			Force: true,
   507  		})
   508  	}); err != nil {
   509  		return err
   510  	}
   511  
   512  	objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
   513  
   514  	// Don't return error below this
   515  	if err := PushUpdate(
   516  		&repo_module.PushUpdateOptions{
   517  			RefFullName:  git.RefNameFromBranch(branchName),
   518  			OldCommitID:  commit.ID.String(),
   519  			NewCommitID:  objectFormat.EmptyObjectID().String(),
   520  			PusherID:     doer.ID,
   521  			PusherName:   doer.Name,
   522  			RepoUserName: repo.OwnerName,
   523  			RepoName:     repo.Name,
   524  		}); err != nil {
   525  		log.Error("Update: %v", err)
   526  	}
   527  
   528  	return nil
   529  }
   530  
   531  type BranchSyncOptions struct {
   532  	RepoID int64
   533  }
   534  
   535  // branchSyncQueue represents a queue to handle branch sync jobs.
   536  var branchSyncQueue *queue.WorkerPoolQueue[*BranchSyncOptions]
   537  
   538  func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions {
   539  	for _, opts := range items {
   540  		_, err := repo_module.SyncRepoBranches(graceful.GetManager().ShutdownContext(), opts.RepoID, 0)
   541  		if err != nil {
   542  			log.Error("syncRepoBranches [%d] failed: %v", opts.RepoID, err)
   543  		}
   544  	}
   545  	return nil
   546  }
   547  
   548  func addRepoToBranchSyncQueue(repoID, doerID int64) error {
   549  	return branchSyncQueue.Push(&BranchSyncOptions{
   550  		RepoID: repoID,
   551  	})
   552  }
   553  
   554  func initBranchSyncQueue(ctx context.Context) error {
   555  	branchSyncQueue = queue.CreateUniqueQueue(ctx, "branch_sync", handlerBranchSync)
   556  	if branchSyncQueue == nil {
   557  		return errors.New("unable to create branch_sync queue")
   558  	}
   559  	go graceful.GetManager().RunWithCancel(branchSyncQueue)
   560  
   561  	return nil
   562  }
   563  
   564  func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error {
   565  	if err := db.Iterate(ctx, builder.Eq{"is_empty": false}, func(ctx context.Context, repo *repo_model.Repository) error {
   566  		return addRepoToBranchSyncQueue(repo.ID, doerID)
   567  	}); err != nil {
   568  		return fmt.Errorf("run sync all branches failed: %v", err)
   569  	}
   570  	return nil
   571  }
   572  
   573  func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, newBranchName string) error {
   574  	if repo.DefaultBranch == newBranchName {
   575  		return nil
   576  	}
   577  
   578  	if !gitRepo.IsBranchExist(newBranchName) {
   579  		return git_model.ErrBranchNotExist{
   580  			BranchName: newBranchName,
   581  		}
   582  	}
   583  
   584  	oldDefaultBranchName := repo.DefaultBranch
   585  	repo.DefaultBranch = newBranchName
   586  	if err := db.WithTx(ctx, func(ctx context.Context) error {
   587  		if err := repo_model.UpdateDefaultBranch(ctx, repo); err != nil {
   588  			return err
   589  		}
   590  
   591  		if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
   592  			log.Error("DeleteCronTaskByRepo: %v", err)
   593  		}
   594  		// cancel running cron jobs of this repository and delete old schedules
   595  		if err := actions_model.CancelPreviousJobs(
   596  			ctx,
   597  			repo.ID,
   598  			oldDefaultBranchName,
   599  			"",
   600  			webhook_module.HookEventSchedule,
   601  		); err != nil {
   602  			log.Error("CancelPreviousJobs: %v", err)
   603  		}
   604  
   605  		if err := gitrepo.SetDefaultBranch(ctx, repo, newBranchName); err != nil {
   606  			if !git.IsErrUnsupportedVersion(err) {
   607  				return err
   608  			}
   609  		}
   610  		return nil
   611  	}); err != nil {
   612  		return err
   613  	}
   614  
   615  	notify_service.ChangeDefaultBranch(ctx, repo)
   616  
   617  	return nil
   618  }