Sync branches into databases (#22743)
Related #14180 Related #25233 Related #22639 Close #19786 Related #12763 This PR will change all the branches retrieve method from reading git data to read database to reduce git read operations. - [x] Sync git branches information into database when push git data - [x] Create a new table `Branch`, merge some columns of `DeletedBranch` into `Branch` table and drop the table `DeletedBranch`. - [x] Read `Branch` table when visit `code` -> `branch` page - [x] Read `Branch` table when list branch names in `code` page dropdown - [x] Read `Branch` table when list git ref compare page - [x] Provide a button in admin page to manually sync all branches. - [x] Sync branches if repository is not empty but database branches are empty when visiting pages with branches list - [x] Use `commit_time desc` as the default FindBranch order by to keep consistent as before and deleted branches will be always at the end. --------- Co-authored-by: Jason Song <i@wolfogre.com>
This commit is contained in:
		
							parent
							
								
									5a871932f0
								
							
						
					
					
						commit
						6e19484f4d
					
				| 
						 | 
					@ -318,90 +318,6 @@ func (err ErrFilePathProtected) Unwrap() error {
 | 
				
			||||||
	return util.ErrPermissionDenied
 | 
						return util.ErrPermissionDenied
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// __________                             .__
 | 
					 | 
				
			||||||
// \______   \____________    ____   ____ |  |__
 | 
					 | 
				
			||||||
//  |    |  _/\_  __ \__  \  /    \_/ ___\|  |  \
 | 
					 | 
				
			||||||
//  |    |   \ |  | \// __ \|   |  \  \___|   Y  \
 | 
					 | 
				
			||||||
//  |______  / |__|  (____  /___|  /\___  >___|  /
 | 
					 | 
				
			||||||
//         \/             \/     \/     \/     \/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ErrBranchDoesNotExist represents an error that branch with such name does not exist.
 | 
					 | 
				
			||||||
type ErrBranchDoesNotExist struct {
 | 
					 | 
				
			||||||
	BranchName string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsErrBranchDoesNotExist checks if an error is an ErrBranchDoesNotExist.
 | 
					 | 
				
			||||||
func IsErrBranchDoesNotExist(err error) bool {
 | 
					 | 
				
			||||||
	_, ok := err.(ErrBranchDoesNotExist)
 | 
					 | 
				
			||||||
	return ok
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (err ErrBranchDoesNotExist) Error() string {
 | 
					 | 
				
			||||||
	return fmt.Sprintf("branch does not exist [name: %s]", err.BranchName)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (err ErrBranchDoesNotExist) Unwrap() error {
 | 
					 | 
				
			||||||
	return util.ErrNotExist
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ErrBranchAlreadyExists represents an error that branch with such name already exists.
 | 
					 | 
				
			||||||
type ErrBranchAlreadyExists struct {
 | 
					 | 
				
			||||||
	BranchName string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists.
 | 
					 | 
				
			||||||
func IsErrBranchAlreadyExists(err error) bool {
 | 
					 | 
				
			||||||
	_, ok := err.(ErrBranchAlreadyExists)
 | 
					 | 
				
			||||||
	return ok
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (err ErrBranchAlreadyExists) Error() string {
 | 
					 | 
				
			||||||
	return fmt.Sprintf("branch already exists [name: %s]", err.BranchName)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (err ErrBranchAlreadyExists) Unwrap() error {
 | 
					 | 
				
			||||||
	return util.ErrAlreadyExist
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ErrBranchNameConflict represents an error that branch name conflicts with other branch.
 | 
					 | 
				
			||||||
type ErrBranchNameConflict struct {
 | 
					 | 
				
			||||||
	BranchName string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict.
 | 
					 | 
				
			||||||
func IsErrBranchNameConflict(err error) bool {
 | 
					 | 
				
			||||||
	_, ok := err.(ErrBranchNameConflict)
 | 
					 | 
				
			||||||
	return ok
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (err ErrBranchNameConflict) Error() string {
 | 
					 | 
				
			||||||
	return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (err ErrBranchNameConflict) Unwrap() error {
 | 
					 | 
				
			||||||
	return util.ErrAlreadyExist
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ErrBranchesEqual represents an error that branch name conflicts with other branch.
 | 
					 | 
				
			||||||
type ErrBranchesEqual struct {
 | 
					 | 
				
			||||||
	BaseBranchName string
 | 
					 | 
				
			||||||
	HeadBranchName string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// IsErrBranchesEqual checks if an error is an ErrBranchesEqual.
 | 
					 | 
				
			||||||
func IsErrBranchesEqual(err error) bool {
 | 
					 | 
				
			||||||
	_, ok := err.(ErrBranchesEqual)
 | 
					 | 
				
			||||||
	return ok
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (err ErrBranchesEqual) Error() string {
 | 
					 | 
				
			||||||
	return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (err ErrBranchesEqual) Unwrap() error {
 | 
					 | 
				
			||||||
	return util.ErrInvalidArgument
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it.
 | 
					// ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it.
 | 
				
			||||||
type ErrDisallowedToMerge struct {
 | 
					type ErrDisallowedToMerge struct {
 | 
				
			||||||
	Reason string
 | 
						Reason string
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 1
 | 
				
			||||||
 | 
					  repo_id: 1
 | 
				
			||||||
 | 
					  name: 'foo'
 | 
				
			||||||
 | 
					  commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
 | 
				
			||||||
 | 
					  commit_message: 'first commit'
 | 
				
			||||||
 | 
					  commit_time: 978307100
 | 
				
			||||||
 | 
					  pusher_id: 1
 | 
				
			||||||
 | 
					  is_deleted: true
 | 
				
			||||||
 | 
					  deleted_by_id: 1
 | 
				
			||||||
 | 
					  deleted_unix: 978307200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 2
 | 
				
			||||||
 | 
					  repo_id: 1
 | 
				
			||||||
 | 
					  name: 'bar'
 | 
				
			||||||
 | 
					  commit_id: '62fb502a7172d4453f0322a2cc85bddffa57f07a'
 | 
				
			||||||
 | 
					  commit_message: 'second commit'
 | 
				
			||||||
 | 
					  commit_time: 978307100
 | 
				
			||||||
 | 
					  pusher_id: 1
 | 
				
			||||||
 | 
					  is_deleted: true
 | 
				
			||||||
 | 
					  deleted_by_id: 99
 | 
				
			||||||
 | 
					  deleted_unix: 978307200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 3
 | 
				
			||||||
 | 
					  repo_id: 1
 | 
				
			||||||
 | 
					  name: 'branch2'
 | 
				
			||||||
 | 
					  commit_id: '985f0301dba5e7b34be866819cd15ad3d8f508ee'
 | 
				
			||||||
 | 
					  commit_message: 'make pull5 outdated'
 | 
				
			||||||
 | 
					  commit_time: 1579166279
 | 
				
			||||||
 | 
					  pusher_id: 1
 | 
				
			||||||
 | 
					  is_deleted: false
 | 
				
			||||||
 | 
					  deleted_by_id: 0
 | 
				
			||||||
 | 
					  deleted_unix: 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 4
 | 
				
			||||||
 | 
					  repo_id: 1
 | 
				
			||||||
 | 
					  name: 'master'
 | 
				
			||||||
 | 
					  commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
 | 
				
			||||||
 | 
					  commit_message: 'Initial commit'
 | 
				
			||||||
 | 
					  commit_time: 1489927679
 | 
				
			||||||
 | 
					  pusher_id: 1
 | 
				
			||||||
 | 
					  is_deleted: false
 | 
				
			||||||
 | 
					  deleted_by_id: 0
 | 
				
			||||||
 | 
					  deleted_unix: 0
 | 
				
			||||||
| 
						 | 
					@ -1,15 +0,0 @@
 | 
				
			||||||
-
 | 
					 | 
				
			||||||
  id: 1
 | 
					 | 
				
			||||||
  repo_id: 1
 | 
					 | 
				
			||||||
  name: foo
 | 
					 | 
				
			||||||
  commit: 1213212312313213213132131
 | 
					 | 
				
			||||||
  deleted_by_id: 1
 | 
					 | 
				
			||||||
  deleted_unix: 978307200
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
-
 | 
					 | 
				
			||||||
  id: 2
 | 
					 | 
				
			||||||
  repo_id: 1
 | 
					 | 
				
			||||||
  name: bar
 | 
					 | 
				
			||||||
  commit: 5655464564554545466464655
 | 
					 | 
				
			||||||
  deleted_by_id: 99
 | 
					 | 
				
			||||||
  deleted_unix: 978307200
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,379 @@
 | 
				
			||||||
 | 
					// Copyright 2016 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrBranchNotExist represents an error that branch with such name does not exist.
 | 
				
			||||||
 | 
					type ErrBranchNotExist struct {
 | 
				
			||||||
 | 
						RepoID     int64
 | 
				
			||||||
 | 
						BranchName string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsErrBranchNotExist checks if an error is an ErrBranchDoesNotExist.
 | 
				
			||||||
 | 
					func IsErrBranchNotExist(err error) bool {
 | 
				
			||||||
 | 
						_, ok := err.(ErrBranchNotExist)
 | 
				
			||||||
 | 
						return ok
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (err ErrBranchNotExist) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("branch does not exist [repo_id: %d name: %s]", err.RepoID, err.BranchName)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (err ErrBranchNotExist) Unwrap() error {
 | 
				
			||||||
 | 
						return util.ErrNotExist
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrBranchAlreadyExists represents an error that branch with such name already exists.
 | 
				
			||||||
 | 
					type ErrBranchAlreadyExists struct {
 | 
				
			||||||
 | 
						BranchName string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists.
 | 
				
			||||||
 | 
					func IsErrBranchAlreadyExists(err error) bool {
 | 
				
			||||||
 | 
						_, ok := err.(ErrBranchAlreadyExists)
 | 
				
			||||||
 | 
						return ok
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (err ErrBranchAlreadyExists) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("branch already exists [name: %s]", err.BranchName)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (err ErrBranchAlreadyExists) Unwrap() error {
 | 
				
			||||||
 | 
						return util.ErrAlreadyExist
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrBranchNameConflict represents an error that branch name conflicts with other branch.
 | 
				
			||||||
 | 
					type ErrBranchNameConflict struct {
 | 
				
			||||||
 | 
						BranchName string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict.
 | 
				
			||||||
 | 
					func IsErrBranchNameConflict(err error) bool {
 | 
				
			||||||
 | 
						_, ok := err.(ErrBranchNameConflict)
 | 
				
			||||||
 | 
						return ok
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (err ErrBranchNameConflict) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (err ErrBranchNameConflict) Unwrap() error {
 | 
				
			||||||
 | 
						return util.ErrAlreadyExist
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrBranchesEqual represents an error that base branch is equal to the head branch.
 | 
				
			||||||
 | 
					type ErrBranchesEqual struct {
 | 
				
			||||||
 | 
						BaseBranchName string
 | 
				
			||||||
 | 
						HeadBranchName string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsErrBranchesEqual checks if an error is an ErrBranchesEqual.
 | 
				
			||||||
 | 
					func IsErrBranchesEqual(err error) bool {
 | 
				
			||||||
 | 
						_, ok := err.(ErrBranchesEqual)
 | 
				
			||||||
 | 
						return ok
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (err ErrBranchesEqual) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (err ErrBranchesEqual) Unwrap() error {
 | 
				
			||||||
 | 
						return util.ErrInvalidArgument
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Branch represents a branch of a repository
 | 
				
			||||||
 | 
					// For those repository who have many branches, stored into database is a good choice
 | 
				
			||||||
 | 
					// for pagination, keyword search and filtering
 | 
				
			||||||
 | 
					type Branch struct {
 | 
				
			||||||
 | 
						ID            int64
 | 
				
			||||||
 | 
						RepoID        int64  `xorm:"UNIQUE(s)"`
 | 
				
			||||||
 | 
						Name          string `xorm:"UNIQUE(s) NOT NULL"`
 | 
				
			||||||
 | 
						CommitID      string
 | 
				
			||||||
 | 
						CommitMessage string `xorm:"TEXT"`
 | 
				
			||||||
 | 
						PusherID      int64
 | 
				
			||||||
 | 
						Pusher        *user_model.User `xorm:"-"`
 | 
				
			||||||
 | 
						IsDeleted     bool             `xorm:"index"`
 | 
				
			||||||
 | 
						DeletedByID   int64
 | 
				
			||||||
 | 
						DeletedBy     *user_model.User   `xorm:"-"`
 | 
				
			||||||
 | 
						DeletedUnix   timeutil.TimeStamp `xorm:"index"`
 | 
				
			||||||
 | 
						CommitTime    timeutil.TimeStamp // The commit
 | 
				
			||||||
 | 
						CreatedUnix   timeutil.TimeStamp `xorm:"created"`
 | 
				
			||||||
 | 
						UpdatedUnix   timeutil.TimeStamp `xorm:"updated"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (b *Branch) LoadDeletedBy(ctx context.Context) (err error) {
 | 
				
			||||||
 | 
						if b.DeletedBy == nil {
 | 
				
			||||||
 | 
							b.DeletedBy, err = user_model.GetUserByID(ctx, b.DeletedByID)
 | 
				
			||||||
 | 
							if user_model.IsErrUserNotExist(err) {
 | 
				
			||||||
 | 
								b.DeletedBy = user_model.NewGhostUser()
 | 
				
			||||||
 | 
								err = nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (b *Branch) LoadPusher(ctx context.Context) (err error) {
 | 
				
			||||||
 | 
						if b.Pusher == nil && b.PusherID > 0 {
 | 
				
			||||||
 | 
							b.Pusher, err = user_model.GetUserByID(ctx, b.PusherID)
 | 
				
			||||||
 | 
							if user_model.IsErrUserNotExist(err) {
 | 
				
			||||||
 | 
								b.Pusher = user_model.NewGhostUser()
 | 
				
			||||||
 | 
								err = nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						db.RegisterModel(new(Branch))
 | 
				
			||||||
 | 
						db.RegisterModel(new(RenamedBranch))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, error) {
 | 
				
			||||||
 | 
						var branch Branch
 | 
				
			||||||
 | 
						has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						} else if !has {
 | 
				
			||||||
 | 
							return nil, ErrBranchNotExist{
 | 
				
			||||||
 | 
								RepoID:     repoID,
 | 
				
			||||||
 | 
								BranchName: branchName,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &branch, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AddBranches(ctx context.Context, branches []*Branch) error {
 | 
				
			||||||
 | 
						for _, branch := range branches {
 | 
				
			||||||
 | 
							if _, err := db.GetEngine(ctx).Insert(branch); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetDeletedBranchByID(ctx context.Context, repoID, branchID int64) (*Branch, error) {
 | 
				
			||||||
 | 
						var branch Branch
 | 
				
			||||||
 | 
						has, err := db.GetEngine(ctx).ID(branchID).Get(&branch)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						} else if !has {
 | 
				
			||||||
 | 
							return nil, ErrBranchNotExist{
 | 
				
			||||||
 | 
								RepoID: repoID,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if branch.RepoID != repoID {
 | 
				
			||||||
 | 
							return nil, ErrBranchNotExist{
 | 
				
			||||||
 | 
								RepoID: repoID,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !branch.IsDeleted {
 | 
				
			||||||
 | 
							return nil, ErrBranchNotExist{
 | 
				
			||||||
 | 
								RepoID: repoID,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &branch, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64) error {
 | 
				
			||||||
 | 
						return db.WithTx(ctx, func(ctx context.Context) error {
 | 
				
			||||||
 | 
							branches := make([]*Branch, 0, len(branchIDs))
 | 
				
			||||||
 | 
							if err := db.GetEngine(ctx).In("id", branchIDs).Find(&branches); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, branch := range branches {
 | 
				
			||||||
 | 
								if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateBranch updates the branch information in the database. If the branch exist, it will update latest commit of this branch information
 | 
				
			||||||
 | 
					// If it doest not exist, insert a new record into database
 | 
				
			||||||
 | 
					func UpdateBranch(ctx context.Context, repoID int64, branchName, commitID, commitMessage string, pusherID int64, commitTime time.Time) error {
 | 
				
			||||||
 | 
						cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName).
 | 
				
			||||||
 | 
							Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix").
 | 
				
			||||||
 | 
							Update(&Branch{
 | 
				
			||||||
 | 
								CommitID:      commitID,
 | 
				
			||||||
 | 
								CommitMessage: commitMessage,
 | 
				
			||||||
 | 
								PusherID:      pusherID,
 | 
				
			||||||
 | 
								CommitTime:    timeutil.TimeStamp(commitTime.Unix()),
 | 
				
			||||||
 | 
								IsDeleted:     false,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if cnt > 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return db.Insert(ctx, &Branch{
 | 
				
			||||||
 | 
							RepoID:        repoID,
 | 
				
			||||||
 | 
							Name:          branchName,
 | 
				
			||||||
 | 
							CommitID:      commitID,
 | 
				
			||||||
 | 
							CommitMessage: commitMessage,
 | 
				
			||||||
 | 
							PusherID:      pusherID,
 | 
				
			||||||
 | 
							CommitTime:    timeutil.TimeStamp(commitTime.Unix()),
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AddDeletedBranch adds a deleted branch to the database
 | 
				
			||||||
 | 
					func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error {
 | 
				
			||||||
 | 
						branch, err := GetBranch(ctx, repoID, branchName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if branch.IsDeleted {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=? AND is_deleted=?", repoID, branchName, false).
 | 
				
			||||||
 | 
							Cols("is_deleted, deleted_by_id, deleted_unix").
 | 
				
			||||||
 | 
							Update(&Branch{
 | 
				
			||||||
 | 
								IsDeleted:   true,
 | 
				
			||||||
 | 
								DeletedByID: deletedByID,
 | 
				
			||||||
 | 
								DeletedUnix: timeutil.TimeStampNow(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if cnt == 0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("branch %s not found or has been deleted", branchName)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func RemoveDeletedBranchByID(ctx context.Context, repoID, branchID int64) error {
 | 
				
			||||||
 | 
						_, err := db.GetEngine(ctx).Where("repo_id=? AND id=? AND is_deleted = ?", repoID, branchID, true).Delete(new(Branch))
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RemoveOldDeletedBranches removes old deleted branches
 | 
				
			||||||
 | 
					func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) {
 | 
				
			||||||
 | 
						// Nothing to do for shutdown or terminate
 | 
				
			||||||
 | 
						log.Trace("Doing: DeletedBranchesCleanup")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						deleteBefore := time.Now().Add(-olderThan)
 | 
				
			||||||
 | 
						_, err := db.GetEngine(ctx).Where("is_deleted=? AND deleted_unix < ?", true, deleteBefore.Unix()).Delete(new(Branch))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("DeletedBranchesCleanup: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RenamedBranch provide renamed branch log
 | 
				
			||||||
 | 
					// will check it when a branch can't be found
 | 
				
			||||||
 | 
					type RenamedBranch struct {
 | 
				
			||||||
 | 
						ID          int64 `xorm:"pk autoincr"`
 | 
				
			||||||
 | 
						RepoID      int64 `xorm:"INDEX NOT NULL"`
 | 
				
			||||||
 | 
						From        string
 | 
				
			||||||
 | 
						To          string
 | 
				
			||||||
 | 
						CreatedUnix timeutil.TimeStamp `xorm:"created"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FindRenamedBranch check if a branch was renamed
 | 
				
			||||||
 | 
					func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) {
 | 
				
			||||||
 | 
						branch = &RenamedBranch{
 | 
				
			||||||
 | 
							RepoID: repoID,
 | 
				
			||||||
 | 
							From:   from,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						exist, err = db.GetEngine(ctx).Get(branch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return branch, exist, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RenameBranch rename a branch
 | 
				
			||||||
 | 
					func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) {
 | 
				
			||||||
 | 
						ctx, committer, err := db.TxContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer committer.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sess := db.GetEngine(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 1. update branch in database
 | 
				
			||||||
 | 
						if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
 | 
				
			||||||
 | 
							Name: to,
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						} else if n <= 0 {
 | 
				
			||||||
 | 
							return ErrBranchNotExist{
 | 
				
			||||||
 | 
								RepoID:     repo.ID,
 | 
				
			||||||
 | 
								BranchName: from,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 2. update default branch if needed
 | 
				
			||||||
 | 
						isDefault := repo.DefaultBranch == from
 | 
				
			||||||
 | 
						if isDefault {
 | 
				
			||||||
 | 
							repo.DefaultBranch = to
 | 
				
			||||||
 | 
							_, err = sess.ID(repo.ID).Cols("default_branch").Update(repo)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 3. Update protected branch if needed
 | 
				
			||||||
 | 
						protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if protectedBranch != nil {
 | 
				
			||||||
 | 
							// there is a protect rule for this branch
 | 
				
			||||||
 | 
							protectedBranch.RuleName = to
 | 
				
			||||||
 | 
							_, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// some glob protect rules may match this branch
 | 
				
			||||||
 | 
							protected, err := IsBranchProtected(ctx, repo.ID, from)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if protected {
 | 
				
			||||||
 | 
								return ErrBranchIsProtected
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 4. Update all not merged pull request base branch name
 | 
				
			||||||
 | 
						_, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?",
 | 
				
			||||||
 | 
							repo.ID, from, false).
 | 
				
			||||||
 | 
							Update(map[string]interface{}{"base_branch": to})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 5. do git action
 | 
				
			||||||
 | 
						if err = gitAction(isDefault); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 6. insert renamed branch record
 | 
				
			||||||
 | 
						renamedBranch := &RenamedBranch{
 | 
				
			||||||
 | 
							RepoID: repo.ID,
 | 
				
			||||||
 | 
							From:   from,
 | 
				
			||||||
 | 
							To:     to,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = db.Insert(ctx, renamedBranch)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return committer.Commit()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,132 @@
 | 
				
			||||||
 | 
					// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/container"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"xorm.io/builder"
 | 
				
			||||||
 | 
						"xorm.io/xorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type BranchList []*Branch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (branches BranchList) LoadDeletedBy(ctx context.Context) error {
 | 
				
			||||||
 | 
						ids := container.Set[int64]{}
 | 
				
			||||||
 | 
						for _, branch := range branches {
 | 
				
			||||||
 | 
							if !branch.IsDeleted {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ids.Add(branch.DeletedByID)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						usersMap := make(map[int64]*user_model.User, len(ids))
 | 
				
			||||||
 | 
						if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, branch := range branches {
 | 
				
			||||||
 | 
							if !branch.IsDeleted {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							branch.DeletedBy = usersMap[branch.DeletedByID]
 | 
				
			||||||
 | 
							if branch.DeletedBy == nil {
 | 
				
			||||||
 | 
								branch.DeletedBy = user_model.NewGhostUser()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (branches BranchList) LoadPusher(ctx context.Context) error {
 | 
				
			||||||
 | 
						ids := container.Set[int64]{}
 | 
				
			||||||
 | 
						for _, branch := range branches {
 | 
				
			||||||
 | 
							if branch.PusherID > 0 { // pusher_id maybe zero because some branches are sync by backend with no pusher
 | 
				
			||||||
 | 
								ids.Add(branch.PusherID)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						usersMap := make(map[int64]*user_model.User, len(ids))
 | 
				
			||||||
 | 
						if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, branch := range branches {
 | 
				
			||||||
 | 
							if branch.PusherID <= 0 {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							branch.Pusher = usersMap[branch.PusherID]
 | 
				
			||||||
 | 
							if branch.Pusher == nil {
 | 
				
			||||||
 | 
								branch.Pusher = user_model.NewGhostUser()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						BranchOrderByNameAsc        = "name ASC"
 | 
				
			||||||
 | 
						BranchOrderByCommitTimeDesc = "commit_time DESC"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FindBranchOptions struct {
 | 
				
			||||||
 | 
						db.ListOptions
 | 
				
			||||||
 | 
						RepoID             int64
 | 
				
			||||||
 | 
						ExcludeBranchNames []string
 | 
				
			||||||
 | 
						IsDeletedBranch    util.OptionalBool
 | 
				
			||||||
 | 
						OrderBy            string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (opts *FindBranchOptions) Cond() builder.Cond {
 | 
				
			||||||
 | 
						cond := builder.NewCond()
 | 
				
			||||||
 | 
						if opts.RepoID > 0 {
 | 
				
			||||||
 | 
							cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(opts.ExcludeBranchNames) > 0 {
 | 
				
			||||||
 | 
							cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !opts.IsDeletedBranch.IsNone() {
 | 
				
			||||||
 | 
							cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.IsTrue()})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return cond
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) {
 | 
				
			||||||
 | 
						return db.GetEngine(ctx).Where(opts.Cond()).Count(&Branch{})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session {
 | 
				
			||||||
 | 
						if !opts.IsDeletedBranch.IsFalse() { // if deleted branch included, put them at the end
 | 
				
			||||||
 | 
							sess = sess.OrderBy("is_deleted ASC")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if opts.OrderBy == "" {
 | 
				
			||||||
 | 
							opts.OrderBy = BranchOrderByCommitTimeDesc
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return sess.OrderBy(opts.OrderBy)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) {
 | 
				
			||||||
 | 
						sess := db.GetEngine(ctx).Where(opts.Cond())
 | 
				
			||||||
 | 
						if opts.PageSize > 0 && !opts.IsListAll() {
 | 
				
			||||||
 | 
							sess = db.SetSessionPagination(sess, &opts.ListOptions)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sess = orderByBranches(sess, opts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var branches []*Branch
 | 
				
			||||||
 | 
						return branches, sess.Find(&branches)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) {
 | 
				
			||||||
 | 
						sess := db.GetEngine(ctx).Select("name").Where(opts.Cond())
 | 
				
			||||||
 | 
						if opts.PageSize > 0 && !opts.IsListAll() {
 | 
				
			||||||
 | 
							sess = db.SetSessionPagination(sess, &opts.ListOptions)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sess = orderByBranches(sess, opts)
 | 
				
			||||||
 | 
						var branches []string
 | 
				
			||||||
 | 
						if err := sess.Table("branch").Find(&branches); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return branches, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ import (
 | 
				
			||||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
						issues_model "code.gitea.io/gitea/models/issues"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unittest"
 | 
						"code.gitea.io/gitea/models/unittest"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -18,24 +19,37 @@ import (
 | 
				
			||||||
func TestAddDeletedBranch(t *testing.T) {
 | 
					func TestAddDeletedBranch(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
						repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
				
			||||||
	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
 | 
						firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Error(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID))
 | 
						assert.True(t, firstBranch.IsDeleted)
 | 
				
			||||||
	assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "test", "5655464564554545466464656", int64(1)))
 | 
						assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.DeletedByID))
 | 
				
			||||||
 | 
						assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "branch2", int64(1)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: "branch2"})
 | 
				
			||||||
 | 
						assert.True(t, secondBranch.IsDeleted)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.Name, secondBranch.CommitID, secondBranch.CommitMessage, secondBranch.PusherID, secondBranch.CommitTime.AsLocalTime())
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestGetDeletedBranches(t *testing.T) {
 | 
					func TestGetDeletedBranches(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
						repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	branches, err := git_model.GetDeletedBranches(db.DefaultContext, repo.ID)
 | 
						branches, err := git_model.FindBranches(db.DefaultContext, git_model.FindBranchOptions{
 | 
				
			||||||
 | 
							ListOptions: db.ListOptions{
 | 
				
			||||||
 | 
								ListAll: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							RepoID:          repo.ID,
 | 
				
			||||||
 | 
							IsDeletedBranch: util.OptionalBoolTrue,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	assert.Len(t, branches, 2)
 | 
						assert.Len(t, branches, 2)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestGetDeletedBranch(t *testing.T) {
 | 
					func TestGetDeletedBranch(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
 | 
						firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.NotNil(t, getDeletedBranch(t, firstBranch))
 | 
						assert.NotNil(t, getDeletedBranch(t, firstBranch))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -43,18 +57,18 @@ func TestGetDeletedBranch(t *testing.T) {
 | 
				
			||||||
func TestDeletedBranchLoadUser(t *testing.T) {
 | 
					func TestDeletedBranchLoadUser(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
 | 
						firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
 | 
				
			||||||
	secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2})
 | 
						secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	branch := getDeletedBranch(t, firstBranch)
 | 
						branch := getDeletedBranch(t, firstBranch)
 | 
				
			||||||
	assert.Nil(t, branch.DeletedBy)
 | 
						assert.Nil(t, branch.DeletedBy)
 | 
				
			||||||
	branch.LoadUser(db.DefaultContext)
 | 
						branch.LoadDeletedBy(db.DefaultContext)
 | 
				
			||||||
	assert.NotNil(t, branch.DeletedBy)
 | 
						assert.NotNil(t, branch.DeletedBy)
 | 
				
			||||||
	assert.Equal(t, "user1", branch.DeletedBy.Name)
 | 
						assert.Equal(t, "user1", branch.DeletedBy.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	branch = getDeletedBranch(t, secondBranch)
 | 
						branch = getDeletedBranch(t, secondBranch)
 | 
				
			||||||
	assert.Nil(t, branch.DeletedBy)
 | 
						assert.Nil(t, branch.DeletedBy)
 | 
				
			||||||
	branch.LoadUser(db.DefaultContext)
 | 
						branch.LoadDeletedBy(db.DefaultContext)
 | 
				
			||||||
	assert.NotNil(t, branch.DeletedBy)
 | 
						assert.NotNil(t, branch.DeletedBy)
 | 
				
			||||||
	assert.Equal(t, "Ghost", branch.DeletedBy.Name)
 | 
						assert.Equal(t, "Ghost", branch.DeletedBy.Name)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -63,22 +77,22 @@ func TestRemoveDeletedBranch(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
						repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
 | 
						firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := git_model.RemoveDeletedBranchByID(db.DefaultContext, repo.ID, 1)
 | 
						err := git_model.RemoveDeletedBranchByID(db.DefaultContext, repo.ID, 1)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	unittest.AssertNotExistsBean(t, firstBranch)
 | 
						unittest.AssertNotExistsBean(t, firstBranch)
 | 
				
			||||||
	unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2})
 | 
						unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getDeletedBranch(t *testing.T, branch *git_model.DeletedBranch) *git_model.DeletedBranch {
 | 
					func getDeletedBranch(t *testing.T, branch *git_model.Branch) *git_model.Branch {
 | 
				
			||||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
						repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo.ID, branch.ID)
 | 
						deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo.ID, branch.ID)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	assert.Equal(t, branch.ID, deletedBranch.ID)
 | 
						assert.Equal(t, branch.ID, deletedBranch.ID)
 | 
				
			||||||
	assert.Equal(t, branch.Name, deletedBranch.Name)
 | 
						assert.Equal(t, branch.Name, deletedBranch.Name)
 | 
				
			||||||
	assert.Equal(t, branch.Commit, deletedBranch.Commit)
 | 
						assert.Equal(t, branch.CommitID, deletedBranch.CommitID)
 | 
				
			||||||
	assert.Equal(t, branch.DeletedByID, deletedBranch.DeletedByID)
 | 
						assert.Equal(t, branch.DeletedByID, deletedBranch.DeletedByID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return deletedBranch
 | 
						return deletedBranch
 | 
				
			||||||
| 
						 | 
					@ -146,8 +160,8 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo2.ID, 1)
 | 
						deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo2.ID, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Expect no error, and the returned branch is nil.
 | 
						// Expect error, and the returned branch is nil.
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.Error(t, err)
 | 
				
			||||||
	assert.Nil(t, deletedBranch)
 | 
						assert.Nil(t, deletedBranch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Now get the deletedBranch with ID of 1 on repo with ID 1.
 | 
						// Now get the deletedBranch with ID of 1 on repo with ID 1.
 | 
				
			||||||
| 
						 | 
					@ -1,197 +0,0 @@
 | 
				
			||||||
// Copyright 2016 The Gitea Authors. All rights reserved.
 | 
					 | 
				
			||||||
// SPDX-License-Identifier: MIT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package git
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"context"
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
					 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
					 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// DeletedBranch struct
 | 
					 | 
				
			||||||
type DeletedBranch struct {
 | 
					 | 
				
			||||||
	ID          int64              `xorm:"pk autoincr"`
 | 
					 | 
				
			||||||
	RepoID      int64              `xorm:"UNIQUE(s) INDEX NOT NULL"`
 | 
					 | 
				
			||||||
	Name        string             `xorm:"UNIQUE(s) NOT NULL"`
 | 
					 | 
				
			||||||
	Commit      string             `xorm:"UNIQUE(s) NOT NULL"`
 | 
					 | 
				
			||||||
	DeletedByID int64              `xorm:"INDEX"`
 | 
					 | 
				
			||||||
	DeletedBy   *user_model.User   `xorm:"-"`
 | 
					 | 
				
			||||||
	DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func init() {
 | 
					 | 
				
			||||||
	db.RegisterModel(new(DeletedBranch))
 | 
					 | 
				
			||||||
	db.RegisterModel(new(RenamedBranch))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AddDeletedBranch adds a deleted branch to the database
 | 
					 | 
				
			||||||
func AddDeletedBranch(ctx context.Context, repoID int64, branchName, commit string, deletedByID int64) error {
 | 
					 | 
				
			||||||
	deletedBranch := &DeletedBranch{
 | 
					 | 
				
			||||||
		RepoID:      repoID,
 | 
					 | 
				
			||||||
		Name:        branchName,
 | 
					 | 
				
			||||||
		Commit:      commit,
 | 
					 | 
				
			||||||
		DeletedByID: deletedByID,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	_, err := db.GetEngine(ctx).Insert(deletedBranch)
 | 
					 | 
				
			||||||
	return err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetDeletedBranches returns all the deleted branches
 | 
					 | 
				
			||||||
func GetDeletedBranches(ctx context.Context, repoID int64) ([]*DeletedBranch, error) {
 | 
					 | 
				
			||||||
	deletedBranches := make([]*DeletedBranch, 0)
 | 
					 | 
				
			||||||
	return deletedBranches, db.GetEngine(ctx).Where("repo_id = ?", repoID).Desc("deleted_unix").Find(&deletedBranches)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetDeletedBranchByID get a deleted branch by its ID
 | 
					 | 
				
			||||||
func GetDeletedBranchByID(ctx context.Context, repoID, id int64) (*DeletedBranch, error) {
 | 
					 | 
				
			||||||
	deletedBranch := &DeletedBranch{}
 | 
					 | 
				
			||||||
	has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("id = ?", id).Get(deletedBranch)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if !has {
 | 
					 | 
				
			||||||
		return nil, nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return deletedBranch, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// RemoveDeletedBranchByID removes a deleted branch from the database
 | 
					 | 
				
			||||||
func RemoveDeletedBranchByID(ctx context.Context, repoID, id int64) (err error) {
 | 
					 | 
				
			||||||
	deletedBranch := &DeletedBranch{
 | 
					 | 
				
			||||||
		RepoID: repoID,
 | 
					 | 
				
			||||||
		ID:     id,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if affected, err := db.GetEngine(ctx).Delete(deletedBranch); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	} else if affected != 1 {
 | 
					 | 
				
			||||||
		return fmt.Errorf("remove deleted branch ID(%v) failed", id)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// LoadUser loads the user that deleted the branch
 | 
					 | 
				
			||||||
// When there's no user found it returns a user_model.NewGhostUser
 | 
					 | 
				
			||||||
func (deletedBranch *DeletedBranch) LoadUser(ctx context.Context) {
 | 
					 | 
				
			||||||
	user, err := user_model.GetUserByID(ctx, deletedBranch.DeletedByID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		user = user_model.NewGhostUser()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	deletedBranch.DeletedBy = user
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// RemoveDeletedBranchByName removes all deleted branches
 | 
					 | 
				
			||||||
func RemoveDeletedBranchByName(ctx context.Context, repoID int64, branch string) error {
 | 
					 | 
				
			||||||
	_, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch))
 | 
					 | 
				
			||||||
	return err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// RemoveOldDeletedBranches removes old deleted branches
 | 
					 | 
				
			||||||
func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) {
 | 
					 | 
				
			||||||
	// Nothing to do for shutdown or terminate
 | 
					 | 
				
			||||||
	log.Trace("Doing: DeletedBranchesCleanup")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	deleteBefore := time.Now().Add(-olderThan)
 | 
					 | 
				
			||||||
	_, err := db.GetEngine(ctx).Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error("DeletedBranchesCleanup: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// RenamedBranch provide renamed branch log
 | 
					 | 
				
			||||||
// will check it when a branch can't be found
 | 
					 | 
				
			||||||
type RenamedBranch struct {
 | 
					 | 
				
			||||||
	ID          int64 `xorm:"pk autoincr"`
 | 
					 | 
				
			||||||
	RepoID      int64 `xorm:"INDEX NOT NULL"`
 | 
					 | 
				
			||||||
	From        string
 | 
					 | 
				
			||||||
	To          string
 | 
					 | 
				
			||||||
	CreatedUnix timeutil.TimeStamp `xorm:"created"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FindRenamedBranch check if a branch was renamed
 | 
					 | 
				
			||||||
func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) {
 | 
					 | 
				
			||||||
	branch = &RenamedBranch{
 | 
					 | 
				
			||||||
		RepoID: repoID,
 | 
					 | 
				
			||||||
		From:   from,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	exist, err = db.GetEngine(ctx).Get(branch)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return branch, exist, err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// RenameBranch rename a branch
 | 
					 | 
				
			||||||
func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) {
 | 
					 | 
				
			||||||
	ctx, committer, err := db.TxContext(ctx)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer committer.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sess := db.GetEngine(ctx)
 | 
					 | 
				
			||||||
	// 1. update default branch if needed
 | 
					 | 
				
			||||||
	isDefault := repo.DefaultBranch == from
 | 
					 | 
				
			||||||
	if isDefault {
 | 
					 | 
				
			||||||
		repo.DefaultBranch = to
 | 
					 | 
				
			||||||
		_, err = sess.ID(repo.ID).Cols("default_branch").Update(repo)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 2. Update protected branch if needed
 | 
					 | 
				
			||||||
	protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if protectedBranch != nil {
 | 
					 | 
				
			||||||
		protectedBranch.RuleName = to
 | 
					 | 
				
			||||||
		_, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		protected, err := IsBranchProtected(ctx, repo.ID, from)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if protected {
 | 
					 | 
				
			||||||
			return ErrBranchIsProtected
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 3. Update all not merged pull request base branch name
 | 
					 | 
				
			||||||
	_, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?",
 | 
					 | 
				
			||||||
		repo.ID, from, false).
 | 
					 | 
				
			||||||
		Update(map[string]interface{}{"base_branch": to})
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 4. do git action
 | 
					 | 
				
			||||||
	if err = gitAction(isDefault); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 5. insert renamed branch record
 | 
					 | 
				
			||||||
	renamedBranch := &RenamedBranch{
 | 
					 | 
				
			||||||
		RepoID: repo.ID,
 | 
					 | 
				
			||||||
		From:   from,
 | 
					 | 
				
			||||||
		To:     to,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = db.Insert(ctx, renamedBranch)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return committer.Commit()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ import (
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gobwas/glob"
 | 
						"github.com/gobwas/glob"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -47,19 +47,32 @@ func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedB
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FindAllMatchedBranches find all matched branches
 | 
					// FindAllMatchedBranches find all matched branches
 | 
				
			||||||
func FindAllMatchedBranches(ctx context.Context, gitRepo *git.Repository, ruleName string) ([]string, error) {
 | 
					func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) {
 | 
				
			||||||
	// FIXME: how many should we get?
 | 
						results := make([]string, 0, 10)
 | 
				
			||||||
	branches, _, err := gitRepo.GetBranchNames(0, 9999999)
 | 
						for page := 1; ; page++ {
 | 
				
			||||||
	if err != nil {
 | 
							brancheNames, err := FindBranchNames(ctx, FindBranchOptions{
 | 
				
			||||||
		return nil, err
 | 
								ListOptions: db.ListOptions{
 | 
				
			||||||
	}
 | 
									PageSize: 100,
 | 
				
			||||||
	rule := glob.MustCompile(ruleName)
 | 
									Page:     page,
 | 
				
			||||||
	results := make([]string, 0, len(branches))
 | 
								},
 | 
				
			||||||
	for _, branch := range branches {
 | 
								RepoID:          repoID,
 | 
				
			||||||
		if rule.Match(branch) {
 | 
								IsDeletedBranch: util.OptionalBoolFalse,
 | 
				
			||||||
			results = append(results, branch)
 | 
							})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							rule := glob.MustCompile(ruleName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, branch := range brancheNames {
 | 
				
			||||||
 | 
								if rule.Match(branch) {
 | 
				
			||||||
 | 
									results = append(results, branch)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(brancheNames) < 100 {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return results, nil
 | 
						return results, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -509,6 +509,8 @@ var migrations = []Migration{
 | 
				
			||||||
	NewMigration("Add TriggerEvent to action_run table", v1_21.AddTriggerEventToActionRun),
 | 
						NewMigration("Add TriggerEvent to action_run table", v1_21.AddTriggerEventToActionRun),
 | 
				
			||||||
	// v263 -> v264
 | 
						// v263 -> v264
 | 
				
			||||||
	NewMigration("Add git_size and lfs_size columns to repository table", v1_21.AddGitSizeAndLFSSizeToRepositoryTable),
 | 
						NewMigration("Add git_size and lfs_size columns to repository table", v1_21.AddGitSizeAndLFSSizeToRepositoryTable),
 | 
				
			||||||
 | 
						// v264 -> v265
 | 
				
			||||||
 | 
						NewMigration("Add branch table", v1_21.AddBranchTable),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetCurrentDBVersion returns the current db version
 | 
					// GetCurrentDBVersion returns the current db version
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,93 @@
 | 
				
			||||||
 | 
					// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package v1_21 //nolint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"xorm.io/xorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AddBranchTable(x *xorm.Engine) error {
 | 
				
			||||||
 | 
						type Branch struct {
 | 
				
			||||||
 | 
							ID            int64
 | 
				
			||||||
 | 
							RepoID        int64  `xorm:"UNIQUE(s)"`
 | 
				
			||||||
 | 
							Name          string `xorm:"UNIQUE(s) NOT NULL"`
 | 
				
			||||||
 | 
							CommitID      string
 | 
				
			||||||
 | 
							CommitMessage string `xorm:"TEXT"`
 | 
				
			||||||
 | 
							PusherID      int64
 | 
				
			||||||
 | 
							IsDeleted     bool `xorm:"index"`
 | 
				
			||||||
 | 
							DeletedByID   int64
 | 
				
			||||||
 | 
							DeletedUnix   timeutil.TimeStamp `xorm:"index"`
 | 
				
			||||||
 | 
							CommitTime    timeutil.TimeStamp // The commit
 | 
				
			||||||
 | 
							CreatedUnix   timeutil.TimeStamp `xorm:"created"`
 | 
				
			||||||
 | 
							UpdatedUnix   timeutil.TimeStamp `xorm:"updated"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := x.Sync(new(Branch)); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if exist, err := x.IsTableExist("deleted_branches"); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						} else if !exist {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type DeletedBranch struct {
 | 
				
			||||||
 | 
							ID          int64
 | 
				
			||||||
 | 
							RepoID      int64  `xorm:"index UNIQUE(s)"`
 | 
				
			||||||
 | 
							Name        string `xorm:"UNIQUE(s) NOT NULL"`
 | 
				
			||||||
 | 
							Commit      string
 | 
				
			||||||
 | 
							DeletedByID int64
 | 
				
			||||||
 | 
							DeletedUnix timeutil.TimeStamp
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var adminUserID int64
 | 
				
			||||||
 | 
						has, err := x.Table("user").
 | 
				
			||||||
 | 
							Select("id").
 | 
				
			||||||
 | 
							Where("is_admin=?", true).
 | 
				
			||||||
 | 
							Asc("id"). // Reliably get the admin with the lowest ID.
 | 
				
			||||||
 | 
							Get(&adminUserID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						} else if !has {
 | 
				
			||||||
 | 
							return fmt.Errorf("no admin user found")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						branches := make([]Branch, 0, 100)
 | 
				
			||||||
 | 
						if err := db.Iterate(context.Background(), nil, func(ctx context.Context, deletedBranch *DeletedBranch) error {
 | 
				
			||||||
 | 
							branches = append(branches, Branch{
 | 
				
			||||||
 | 
								RepoID:      deletedBranch.RepoID,
 | 
				
			||||||
 | 
								Name:        deletedBranch.Name,
 | 
				
			||||||
 | 
								CommitID:    deletedBranch.Commit,
 | 
				
			||||||
 | 
								PusherID:    adminUserID,
 | 
				
			||||||
 | 
								IsDeleted:   true,
 | 
				
			||||||
 | 
								DeletedByID: deletedBranch.DeletedByID,
 | 
				
			||||||
 | 
								DeletedUnix: deletedBranch.DeletedUnix,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							if len(branches) >= 100 {
 | 
				
			||||||
 | 
								_, err := x.Insert(&branches)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								branches = branches[:0]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(branches) > 0 {
 | 
				
			||||||
 | 
							if _, err := x.Insert(&branches); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return x.DropTables("deleted_branches")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -147,7 +147,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 | 
				
			||||||
		&repo_model.Collaboration{RepoID: repoID},
 | 
							&repo_model.Collaboration{RepoID: repoID},
 | 
				
			||||||
		&issues_model.Comment{RefRepoID: repoID},
 | 
							&issues_model.Comment{RefRepoID: repoID},
 | 
				
			||||||
		&git_model.CommitStatus{RepoID: repoID},
 | 
							&git_model.CommitStatus{RepoID: repoID},
 | 
				
			||||||
		&git_model.DeletedBranch{RepoID: repoID},
 | 
							&git_model.Branch{RepoID: repoID},
 | 
				
			||||||
		&git_model.LFSLock{RepoID: repoID},
 | 
							&git_model.LFSLock{RepoID: repoID},
 | 
				
			||||||
		&repo_model.LanguageStat{RepoID: repoID},
 | 
							&repo_model.LanguageStat{RepoID: repoID},
 | 
				
			||||||
		&issues_model.Milestone{RepoID: repoID},
 | 
							&issues_model.Milestone{RepoID: repoID},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1171,9 +1171,9 @@ func GetUserByOpenID(uri string) (*User, error) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetAdminUser returns the first administrator
 | 
					// GetAdminUser returns the first administrator
 | 
				
			||||||
func GetAdminUser() (*User, error) {
 | 
					func GetAdminUser(ctx context.Context) (*User, error) {
 | 
				
			||||||
	var admin User
 | 
						var admin User
 | 
				
			||||||
	has, err := db.GetEngine(db.DefaultContext).
 | 
						has, err := db.GetEngine(ctx).
 | 
				
			||||||
		Where("is_admin=?", true).
 | 
							Where("is_admin=?", true).
 | 
				
			||||||
		Asc("id"). // Reliably get the admin with the lowest ID.
 | 
							Asc("id"). // Reliably get the admin with the lowest ID.
 | 
				
			||||||
		Get(&admin)
 | 
							Get(&admin)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -667,13 +667,38 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.Data["Tags"] = tags
 | 
						ctx.Data["Tags"] = tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
 | 
						branchOpts := git_model.FindBranchOptions{
 | 
				
			||||||
 | 
							RepoID:          ctx.Repo.Repository.ID,
 | 
				
			||||||
 | 
							IsDeletedBranch: util.OptionalBoolFalse,
 | 
				
			||||||
 | 
							ListOptions: db.ListOptions{
 | 
				
			||||||
 | 
								ListAll: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						branchesTotal, err := git_model.CountBranches(ctx, branchOpts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("CountBranches", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// non empty repo should have at least 1 branch, so this repository's branches haven't been synced yet
 | 
				
			||||||
 | 
						if branchesTotal == 0 { // fallback to do a sync immediately
 | 
				
			||||||
 | 
							branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.ServerError("SyncRepoBranches", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FIXME: use paganation and async loading
 | 
				
			||||||
 | 
						branchOpts.ExcludeBranchNames = []string{ctx.Repo.Repository.DefaultBranch}
 | 
				
			||||||
 | 
						brs, err := git_model.FindBranchNames(ctx, branchOpts)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetBranches", err)
 | 
							ctx.ServerError("GetBranches", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.Data["Branches"] = brs
 | 
						// always put default branch on the top
 | 
				
			||||||
	ctx.Data["BranchesCount"] = len(brs)
 | 
						ctx.Data["Branches"] = append(branchOpts.ExcludeBranchNames, brs...)
 | 
				
			||||||
 | 
						ctx.Data["BranchesCount"] = branchesTotal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If not branch selected, try default one.
 | 
						// If not branch selected, try default one.
 | 
				
			||||||
	// If default branch doesn't exist, fall back to some other branch.
 | 
						// If default branch doesn't exist, fall back to some other branch.
 | 
				
			||||||
| 
						 | 
					@ -897,9 +922,9 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
 | 
				
			||||||
		if len(ctx.Params("*")) == 0 {
 | 
							if len(ctx.Params("*")) == 0 {
 | 
				
			||||||
			refName = ctx.Repo.Repository.DefaultBranch
 | 
								refName = ctx.Repo.Repository.DefaultBranch
 | 
				
			||||||
			if !ctx.Repo.GitRepo.IsBranchExist(refName) {
 | 
								if !ctx.Repo.GitRepo.IsBranchExist(refName) {
 | 
				
			||||||
				brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
 | 
									brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1)
 | 
				
			||||||
				if err == nil && len(brs) != 0 {
 | 
									if err == nil && len(brs) != 0 {
 | 
				
			||||||
					refName = brs[0]
 | 
										refName = brs[0].Name
 | 
				
			||||||
				} else if len(brs) == 0 {
 | 
									} else if len(brs) == 0 {
 | 
				
			||||||
					log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
 | 
										log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
 | 
				
			||||||
					ctx.Repo.Repository.MarkAsBrokenEmpty()
 | 
										ctx.Repo.Repository.MarkAsBrokenEmpty()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,135 @@
 | 
				
			||||||
 | 
					// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MIT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/container"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SyncRepoBranches synchronizes branch table with repository branches
 | 
				
			||||||
 | 
					func SyncRepoBranches(ctx context.Context, repoID, doerID int64) (int64, error) {
 | 
				
			||||||
 | 
						repo, err := repo_model.GetRepositoryByID(ctx, repoID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Debug("SyncRepoBranches: in Repo[%d:%s]", repo.ID, repo.FullName())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("OpenRepository[%s]: %w", repo.RepoPath(), err)
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer gitRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doerID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doerID int64) (int64, error) {
 | 
				
			||||||
 | 
						allBranches := container.Set[string]{}
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							branches, _, err := gitRepo.GetBranchNames(0, 0)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches)
 | 
				
			||||||
 | 
							for _, branch := range branches {
 | 
				
			||||||
 | 
								allBranches.Add(branch)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dbBranches := make(map[string]*git_model.Branch)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							branches, err := git_model.FindBranches(ctx, git_model.FindBranchOptions{
 | 
				
			||||||
 | 
								ListOptions: db.ListOptions{
 | 
				
			||||||
 | 
									ListAll: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								RepoID: repo.ID,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, branch := range branches {
 | 
				
			||||||
 | 
								dbBranches[branch.Name] = branch
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var toAdd []*git_model.Branch
 | 
				
			||||||
 | 
						var toUpdate []*git_model.Branch
 | 
				
			||||||
 | 
						var toRemove []int64
 | 
				
			||||||
 | 
						for branch := range allBranches {
 | 
				
			||||||
 | 
							dbb := dbBranches[branch]
 | 
				
			||||||
 | 
							commit, err := gitRepo.GetBranchCommit(branch)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if dbb == nil {
 | 
				
			||||||
 | 
								toAdd = append(toAdd, &git_model.Branch{
 | 
				
			||||||
 | 
									RepoID:        repo.ID,
 | 
				
			||||||
 | 
									Name:          branch,
 | 
				
			||||||
 | 
									CommitID:      commit.ID.String(),
 | 
				
			||||||
 | 
									CommitMessage: commit.CommitMessage,
 | 
				
			||||||
 | 
									PusherID:      doerID,
 | 
				
			||||||
 | 
									CommitTime:    timeutil.TimeStamp(commit.Author.When.Unix()),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							} else if commit.ID.String() != dbb.CommitID {
 | 
				
			||||||
 | 
								toUpdate = append(toUpdate, &git_model.Branch{
 | 
				
			||||||
 | 
									ID:            dbb.ID,
 | 
				
			||||||
 | 
									RepoID:        repo.ID,
 | 
				
			||||||
 | 
									Name:          branch,
 | 
				
			||||||
 | 
									CommitID:      commit.ID.String(),
 | 
				
			||||||
 | 
									CommitMessage: commit.CommitMessage,
 | 
				
			||||||
 | 
									PusherID:      doerID,
 | 
				
			||||||
 | 
									CommitTime:    timeutil.TimeStamp(commit.Author.When.Unix()),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, dbBranch := range dbBranches {
 | 
				
			||||||
 | 
							if !allBranches.Contains(dbBranch.Name) && !dbBranch.IsDeleted {
 | 
				
			||||||
 | 
								toRemove = append(toRemove, dbBranch.ID)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Trace("SyncRepoBranches[%s]: toAdd: %v, toUpdate: %v, toRemove: %v", repo.FullName(), toAdd, toUpdate, toRemove)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(toAdd) == 0 && len(toRemove) == 0 && len(toUpdate) == 0 {
 | 
				
			||||||
 | 
							return int64(len(allBranches)), nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := db.WithTx(ctx, func(subCtx context.Context) error {
 | 
				
			||||||
 | 
							if len(toAdd) > 0 {
 | 
				
			||||||
 | 
								if err := git_model.AddBranches(subCtx, toAdd); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, b := range toUpdate {
 | 
				
			||||||
 | 
								if _, err := db.GetEngine(subCtx).ID(b.ID).
 | 
				
			||||||
 | 
									Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted").
 | 
				
			||||||
 | 
									Update(b); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(toRemove) > 0 {
 | 
				
			||||||
 | 
								if err := git_model.DeleteBranches(subCtx, repo.ID, doerID, toRemove); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return int64(len(allBranches)), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -351,6 +351,12 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re
 | 
				
			||||||
		if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
 | 
							if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
 | 
				
			||||||
			return fmt.Errorf("setDefaultBranch: %w", err)
 | 
								return fmt.Errorf("setDefaultBranch: %w", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !repo.IsEmpty {
 | 
				
			||||||
 | 
								if _, err := SyncRepoBranches(ctx, repo.ID, u.ID); err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("SyncRepoBranches: %w", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = UpdateRepository(ctx, repo, false); err != nil {
 | 
						if err = UpdateRepository(ctx, repo, false); err != nil {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -151,6 +151,10 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, err := SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil {
 | 
				
			||||||
 | 
								return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !opts.Releases {
 | 
							if !opts.Releases {
 | 
				
			||||||
			// note: this will greatly improve release (tag) sync
 | 
								// note: this will greatly improve release (tag) sync
 | 
				
			||||||
			// for pull-mirrors with many tags
 | 
								// for pull-mirrors with many tags
 | 
				
			||||||
| 
						 | 
					@ -169,7 +173,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx, committer, err := db.TxContext(db.DefaultContext)
 | 
						ctx, committer, err := db.TxContext(ctx)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2660,6 +2660,7 @@ dashboard.delete_repo_archives.started = Delete all repository archives task sta
 | 
				
			||||||
dashboard.delete_missing_repos = Delete all repositories missing their Git files
 | 
					dashboard.delete_missing_repos = Delete all repositories missing their Git files
 | 
				
			||||||
dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started.
 | 
					dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started.
 | 
				
			||||||
dashboard.delete_generated_repository_avatars = Delete generated repository avatars
 | 
					dashboard.delete_generated_repository_avatars = Delete generated repository avatars
 | 
				
			||||||
 | 
					dashboard.sync_repo_branches = Sync missed branches from git data to databases
 | 
				
			||||||
dashboard.update_mirrors = Update Mirrors
 | 
					dashboard.update_mirrors = Update Mirrors
 | 
				
			||||||
dashboard.repo_health_check = Health check all repositories
 | 
					dashboard.repo_health_check = Health check all repositories
 | 
				
			||||||
dashboard.check_repo_stats = Check all repository statistics
 | 
					dashboard.check_repo_stats = Check all repository statistics
 | 
				
			||||||
| 
						 | 
					@ -2713,6 +2714,7 @@ dashboard.gc_lfs = Garbage collect LFS meta objects
 | 
				
			||||||
dashboard.stop_zombie_tasks = Stop zombie tasks
 | 
					dashboard.stop_zombie_tasks = Stop zombie tasks
 | 
				
			||||||
dashboard.stop_endless_tasks = Stop endless tasks
 | 
					dashboard.stop_endless_tasks = Stop endless tasks
 | 
				
			||||||
dashboard.cancel_abandoned_jobs = Cancel abandoned jobs
 | 
					dashboard.cancel_abandoned_jobs = Cancel abandoned jobs
 | 
				
			||||||
 | 
					dashboard.sync_branch.started = Branches Sync started
 | 
				
			||||||
 | 
					
 | 
				
			||||||
users.user_manage_panel = User Account Management
 | 
					users.user_manage_panel = User Account Management
 | 
				
			||||||
users.new_account = Create User Account
 | 
					users.new_account = Create User Account
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,9 @@ import (
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
						repo_module "code.gitea.io/gitea/modules/repository"
 | 
				
			||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/web"
 | 
						"code.gitea.io/gitea/modules/web"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/v1/utils"
 | 
						"code.gitea.io/gitea/routers/api/v1/utils"
 | 
				
			||||||
	"code.gitea.io/gitea/services/convert"
 | 
						"code.gitea.io/gitea/services/convert"
 | 
				
			||||||
| 
						 | 
					@ -76,7 +78,7 @@ func GetBranch(ctx *context.APIContext) {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
 | 
						br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
 | 
							ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
| 
						 | 
					@ -118,6 +120,37 @@ func DeleteBranch(ctx *context.APIContext) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	branchName := ctx.Params("*")
 | 
						branchName := ctx.Params("*")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ctx.Repo.Repository.IsEmpty {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusForbidden, "", "Git Repository is empty.")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check whether branches of this repository has been synced
 | 
				
			||||||
 | 
						totalNumOfBranches, err := git_model.CountBranches(ctx, git_model.FindBranchOptions{
 | 
				
			||||||
 | 
							RepoID:          ctx.Repo.Repository.ID,
 | 
				
			||||||
 | 
							IsDeletedBranch: util.OptionalBoolFalse,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusInternalServerError, "CountBranches", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
 | 
				
			||||||
 | 
							_, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.ServerError("SyncRepoBranches", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ctx.Repo.Repository.IsArchived {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusForbidden, "IsArchived", fmt.Errorf("can not delete branch of an archived repository"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if ctx.Repo.Repository.IsMirror {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusForbidden, "IsMirrored", fmt.Errorf("can not delete branch of an mirror repository"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
 | 
						if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
 | 
				
			||||||
		switch {
 | 
							switch {
 | 
				
			||||||
		case git.IsErrBranchNotExist(err):
 | 
							case git.IsErrBranchNotExist(err):
 | 
				
			||||||
| 
						 | 
					@ -203,14 +236,14 @@ func CreateBranch(ctx *context.APIContext) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
 | 
						err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if models.IsErrBranchDoesNotExist(err) {
 | 
							if git_model.IsErrBranchNotExist(err) {
 | 
				
			||||||
			ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
 | 
								ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if models.IsErrTagAlreadyExists(err) {
 | 
							if models.IsErrTagAlreadyExists(err) {
 | 
				
			||||||
			ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
 | 
								ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
 | 
				
			||||||
		} else if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
 | 
							} else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
 | 
				
			||||||
			ctx.Error(http.StatusConflict, "", "The branch already exists.")
 | 
								ctx.Error(http.StatusConflict, "", "The branch already exists.")
 | 
				
			||||||
		} else if models.IsErrBranchNameConflict(err) {
 | 
							} else if git_model.IsErrBranchNameConflict(err) {
 | 
				
			||||||
			ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
 | 
								ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err)
 | 
								ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err)
 | 
				
			||||||
| 
						 | 
					@ -236,7 +269,7 @@ func CreateBranch(ctx *context.APIContext) {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
 | 
						br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
 | 
							ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
| 
						 | 
					@ -275,20 +308,38 @@ func ListBranches(ctx *context.APIContext) {
 | 
				
			||||||
	//   "200":
 | 
						//   "200":
 | 
				
			||||||
	//     "$ref": "#/responses/BranchList"
 | 
						//     "$ref": "#/responses/BranchList"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var totalNumOfBranches int
 | 
						var totalNumOfBranches int64
 | 
				
			||||||
	var apiBranches []*api.Branch
 | 
						var apiBranches []*api.Branch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	listOptions := utils.GetListOptions(ctx)
 | 
						listOptions := utils.GetListOptions(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil {
 | 
						if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil {
 | 
				
			||||||
 | 
							branchOpts := git_model.FindBranchOptions{
 | 
				
			||||||
 | 
								ListOptions:     listOptions,
 | 
				
			||||||
 | 
								RepoID:          ctx.Repo.Repository.ID,
 | 
				
			||||||
 | 
								IsDeletedBranch: util.OptionalBoolFalse,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							var err error
 | 
				
			||||||
 | 
							totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.Error(http.StatusInternalServerError, "CountBranches", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
 | 
				
			||||||
 | 
								totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									ctx.ServerError("SyncRepoBranches", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
 | 
							rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
 | 
								ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		skip, _ := listOptions.GetStartEnd()
 | 
							branches, err := git_model.FindBranches(ctx, branchOpts)
 | 
				
			||||||
		branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			ctx.Error(http.StatusInternalServerError, "GetBranches", err)
 | 
								ctx.Error(http.StatusInternalServerError, "GetBranches", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
| 
						 | 
					@ -296,11 +347,11 @@ func ListBranches(ctx *context.APIContext) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		apiBranches = make([]*api.Branch, 0, len(branches))
 | 
							apiBranches = make([]*api.Branch, 0, len(branches))
 | 
				
			||||||
		for i := range branches {
 | 
							for i := range branches {
 | 
				
			||||||
			c, err := branches[i].GetCommit()
 | 
								c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				// Skip if this branch doesn't exist anymore.
 | 
									// Skip if this branch doesn't exist anymore.
 | 
				
			||||||
				if git.IsErrNotExist(err) {
 | 
									if git.IsErrNotExist(err) {
 | 
				
			||||||
					total--
 | 
										totalNumOfBranches--
 | 
				
			||||||
					continue
 | 
										continue
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				ctx.Error(http.StatusInternalServerError, "GetCommit", err)
 | 
									ctx.Error(http.StatusInternalServerError, "GetCommit", err)
 | 
				
			||||||
| 
						 | 
					@ -308,19 +359,17 @@ func ListBranches(ctx *context.APIContext) {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			branchProtection := rules.GetFirstMatched(branches[i].Name)
 | 
								branchProtection := rules.GetFirstMatched(branches[i].Name)
 | 
				
			||||||
			apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
 | 
								apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
 | 
									ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			apiBranches = append(apiBranches, apiBranch)
 | 
								apiBranches = append(apiBranches, apiBranch)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		totalNumOfBranches = total
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize)
 | 
						ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
 | 
				
			||||||
	ctx.SetTotalCountHeader(int64(totalNumOfBranches))
 | 
						ctx.SetTotalCountHeader(totalNumOfBranches)
 | 
				
			||||||
	ctx.JSON(http.StatusOK, apiBranches)
 | 
						ctx.JSON(http.StatusOK, apiBranches)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -580,7 +629,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
 | 
				
			||||||
				}()
 | 
									}()
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			// FIXME: since we only need to recheck files protected rules, we could improve this
 | 
								// FIXME: since we only need to recheck files protected rules, we could improve this
 | 
				
			||||||
			matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, ruleName)
 | 
								matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
 | 
									ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
| 
						 | 
					@ -851,7 +900,7 @@ func EditBranchProtection(ctx *context.APIContext) {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// FIXME: since we only need to recheck files protected rules, we could improve this
 | 
								// FIXME: since we only need to recheck files protected rules, we could improve this
 | 
				
			||||||
			matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName)
 | 
								matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
 | 
									ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -687,12 +687,12 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
 | 
				
			||||||
		ctx.Error(http.StatusForbidden, "Access", err)
 | 
							ctx.Error(http.StatusForbidden, "Access", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
 | 
						if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
 | 
				
			||||||
		models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
 | 
							models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
 | 
				
			||||||
		ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
 | 
							ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) {
 | 
						if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
 | 
				
			||||||
		ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
 | 
							ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -843,7 +843,7 @@ func DeleteFile(ctx *context.APIContext) {
 | 
				
			||||||
		if git.IsErrBranchNotExist(err) || models.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
 | 
							if git.IsErrBranchNotExist(err) || models.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
 | 
				
			||||||
			ctx.Error(http.StatusNotFound, "DeleteFile", err)
 | 
								ctx.Error(http.StatusNotFound, "DeleteFile", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		} else if models.IsErrBranchAlreadyExists(err) ||
 | 
							} else if git_model.IsErrBranchAlreadyExists(err) ||
 | 
				
			||||||
			models.IsErrFilenameInvalid(err) ||
 | 
								models.IsErrFilenameInvalid(err) ||
 | 
				
			||||||
			models.IsErrSHADoesNotMatch(err) ||
 | 
								models.IsErrSHADoesNotMatch(err) ||
 | 
				
			||||||
			models.IsErrCommitIDDoesNotMatch(err) ||
 | 
								models.IsErrCommitIDDoesNotMatch(err) ||
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@ import (
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
| 
						 | 
					@ -91,12 +92,12 @@ func ApplyDiffPatch(ctx *context.APIContext) {
 | 
				
			||||||
			ctx.Error(http.StatusForbidden, "Access", err)
 | 
								ctx.Error(http.StatusForbidden, "Access", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
 | 
							if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
 | 
				
			||||||
			models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
 | 
								models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
 | 
				
			||||||
			ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
 | 
								ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) {
 | 
							if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
 | 
				
			||||||
			ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
 | 
								ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,12 +14,15 @@ import (
 | 
				
			||||||
	activities_model "code.gitea.io/gitea/models/activities"
 | 
						activities_model "code.gitea.io/gitea/models/activities"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/graceful"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/json"
 | 
						"code.gitea.io/gitea/modules/json"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/updatechecker"
 | 
						"code.gitea.io/gitea/modules/updatechecker"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/web"
 | 
						"code.gitea.io/gitea/modules/web"
 | 
				
			||||||
	"code.gitea.io/gitea/services/cron"
 | 
						"code.gitea.io/gitea/services/cron"
 | 
				
			||||||
	"code.gitea.io/gitea/services/forms"
 | 
						"code.gitea.io/gitea/services/forms"
 | 
				
			||||||
 | 
						repo_service "code.gitea.io/gitea/services/repository"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
| 
						 | 
					@ -133,12 +136,22 @@ func DashboardPost(ctx *context.Context) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Run operation.
 | 
						// Run operation.
 | 
				
			||||||
	if form.Op != "" {
 | 
						if form.Op != "" {
 | 
				
			||||||
		task := cron.GetTask(form.Op)
 | 
							switch form.Op {
 | 
				
			||||||
		if task != nil {
 | 
							case "sync_repo_branches":
 | 
				
			||||||
			go task.RunWithUser(ctx.Doer, nil)
 | 
								go func() {
 | 
				
			||||||
			ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op)))
 | 
									if err := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), ctx.Doer.ID); err != nil {
 | 
				
			||||||
		} else {
 | 
										log.Error("AddAllRepoBranchesToSyncQueue: %v: %v", ctx.Doer.ID, err)
 | 
				
			||||||
			ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
 | 
									}
 | 
				
			||||||
 | 
								}()
 | 
				
			||||||
 | 
								ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_branch.started"))
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								task := cron.GetTask(form.Op)
 | 
				
			||||||
 | 
								if task != nil {
 | 
				
			||||||
 | 
									go task.RunWithUser(ctx.Doer, nil)
 | 
				
			||||||
 | 
									ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op)))
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if form.From == "monitor" {
 | 
						if form.From == "monitor" {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,6 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	git_model "code.gitea.io/gitea/models/git"
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
					 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
| 
						 | 
					@ -28,32 +27,16 @@ import (
 | 
				
			||||||
	"code.gitea.io/gitea/services/forms"
 | 
						"code.gitea.io/gitea/services/forms"
 | 
				
			||||||
	release_service "code.gitea.io/gitea/services/release"
 | 
						release_service "code.gitea.io/gitea/services/release"
 | 
				
			||||||
	repo_service "code.gitea.io/gitea/services/repository"
 | 
						repo_service "code.gitea.io/gitea/services/repository"
 | 
				
			||||||
	files_service "code.gitea.io/gitea/services/repository/files"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	tplBranch base.TplName = "repo/branch/list"
 | 
						tplBranch base.TplName = "repo/branch/list"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Branch contains the branch information
 | 
					 | 
				
			||||||
type Branch struct {
 | 
					 | 
				
			||||||
	Name              string
 | 
					 | 
				
			||||||
	Commit            *git.Commit
 | 
					 | 
				
			||||||
	IsProtected       bool
 | 
					 | 
				
			||||||
	IsDeleted         bool
 | 
					 | 
				
			||||||
	IsIncluded        bool
 | 
					 | 
				
			||||||
	DeletedBranch     *git_model.DeletedBranch
 | 
					 | 
				
			||||||
	CommitsAhead      int
 | 
					 | 
				
			||||||
	CommitsBehind     int
 | 
					 | 
				
			||||||
	LatestPullRequest *issues_model.PullRequest
 | 
					 | 
				
			||||||
	MergeMovedOn      bool
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Branches render repository branch page
 | 
					// Branches render repository branch page
 | 
				
			||||||
func Branches(ctx *context.Context) {
 | 
					func Branches(ctx *context.Context) {
 | 
				
			||||||
	ctx.Data["Title"] = "Branches"
 | 
						ctx.Data["Title"] = "Branches"
 | 
				
			||||||
	ctx.Data["IsRepoToolbarBranches"] = true
 | 
						ctx.Data["IsRepoToolbarBranches"] = true
 | 
				
			||||||
	ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
 | 
					 | 
				
			||||||
	ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls()
 | 
						ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls()
 | 
				
			||||||
	ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode)
 | 
						ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode)
 | 
				
			||||||
	ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
 | 
						ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
 | 
				
			||||||
| 
						 | 
					@ -68,15 +51,15 @@ func Branches(ctx *context.Context) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pageSize := setting.Git.BranchesRangeSize
 | 
						pageSize := setting.Git.BranchesRangeSize
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	skip := (page - 1) * pageSize
 | 
						defaultBranch, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, util.OptionalBoolNone, page, pageSize)
 | 
				
			||||||
	log.Debug("Branches: skip: %d limit: %d", skip, pageSize)
 | 
						if err != nil {
 | 
				
			||||||
	defaultBranchBranch, branches, branchesCount := loadBranches(ctx, skip, pageSize)
 | 
							ctx.ServerError("LoadBranches", err)
 | 
				
			||||||
	if ctx.Written() {
 | 
					 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Data["Branches"] = branches
 | 
						ctx.Data["Branches"] = branches
 | 
				
			||||||
	ctx.Data["DefaultBranchBranch"] = defaultBranchBranch
 | 
						ctx.Data["DefaultBranchBranch"] = defaultBranch
 | 
				
			||||||
	pager := context.NewPagination(branchesCount, pageSize, page, 5)
 | 
						pager := context.NewPagination(int(branchesCount), pageSize, page, 5)
 | 
				
			||||||
	pager.SetDefaultParams(ctx)
 | 
						pager.SetDefaultParams(ctx)
 | 
				
			||||||
	ctx.Data["Page"] = pager
 | 
						ctx.Data["Page"] = pager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,7 +113,7 @@ func RestoreBranchPost(ctx *context.Context) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{
 | 
						if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{
 | 
				
			||||||
		Remote: ctx.Repo.Repository.RepoPath(),
 | 
							Remote: ctx.Repo.Repository.RepoPath(),
 | 
				
			||||||
		Branch: fmt.Sprintf("%s:%s%s", deletedBranch.Commit, git.BranchPrefix, deletedBranch.Name),
 | 
							Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, git.BranchPrefix, deletedBranch.Name),
 | 
				
			||||||
		Env:    repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
 | 
							Env:    repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		if strings.Contains(err.Error(), "already exists") {
 | 
							if strings.Contains(err.Error(), "already exists") {
 | 
				
			||||||
| 
						 | 
					@ -148,7 +131,7 @@ func RestoreBranchPost(ctx *context.Context) {
 | 
				
			||||||
		&repo_module.PushUpdateOptions{
 | 
							&repo_module.PushUpdateOptions{
 | 
				
			||||||
			RefFullName:  git.RefNameFromBranch(deletedBranch.Name),
 | 
								RefFullName:  git.RefNameFromBranch(deletedBranch.Name),
 | 
				
			||||||
			OldCommitID:  git.EmptySHA,
 | 
								OldCommitID:  git.EmptySHA,
 | 
				
			||||||
			NewCommitID:  deletedBranch.Commit,
 | 
								NewCommitID:  deletedBranch.CommitID,
 | 
				
			||||||
			PusherID:     ctx.Doer.ID,
 | 
								PusherID:     ctx.Doer.ID,
 | 
				
			||||||
			PusherName:   ctx.Doer.Name,
 | 
								PusherName:   ctx.Doer.Name,
 | 
				
			||||||
			RepoUserName: ctx.Repo.Owner.Name,
 | 
								RepoUserName: ctx.Repo.Owner.Name,
 | 
				
			||||||
| 
						 | 
					@ -166,180 +149,6 @@ func redirect(ctx *context.Context) {
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// loadBranches loads branches from the repository limited by page & pageSize.
 | 
					 | 
				
			||||||
// NOTE: May write to context on error.
 | 
					 | 
				
			||||||
func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, int) {
 | 
					 | 
				
			||||||
	defaultBranch, err := ctx.Repo.GitRepo.GetBranch(ctx.Repo.Repository.DefaultBranch)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		if !git.IsErrBranchNotExist(err) {
 | 
					 | 
				
			||||||
			log.Error("loadBranches: get default branch: %v", err)
 | 
					 | 
				
			||||||
			ctx.ServerError("GetDefaultBranch", err)
 | 
					 | 
				
			||||||
			return nil, nil, 0
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		log.Warn("loadBranches: missing default branch %s for %-v", ctx.Repo.Repository.DefaultBranch, ctx.Repo.Repository)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	rawBranches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(skip, limit)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error("GetBranches: %v", err)
 | 
					 | 
				
			||||||
		ctx.ServerError("GetBranches", err)
 | 
					 | 
				
			||||||
		return nil, nil, 0
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		ctx.ServerError("FindRepoProtectedBranchRules", err)
 | 
					 | 
				
			||||||
		return nil, nil, 0
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	repoIDToRepo := map[int64]*repo_model.Repository{}
 | 
					 | 
				
			||||||
	repoIDToRepo[ctx.Repo.Repository.ID] = ctx.Repo.Repository
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	repoIDToGitRepo := map[int64]*git.Repository{}
 | 
					 | 
				
			||||||
	repoIDToGitRepo[ctx.Repo.Repository.ID] = ctx.Repo.GitRepo
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var branches []*Branch
 | 
					 | 
				
			||||||
	for i := range rawBranches {
 | 
					 | 
				
			||||||
		if defaultBranch != nil && rawBranches[i].Name == defaultBranch.Name {
 | 
					 | 
				
			||||||
			// Skip default branch
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		branch := loadOneBranch(ctx, rawBranches[i], defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo)
 | 
					 | 
				
			||||||
		if branch == nil {
 | 
					 | 
				
			||||||
			return nil, nil, 0
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		branches = append(branches, branch)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var defaultBranchBranch *Branch
 | 
					 | 
				
			||||||
	if defaultBranch != nil {
 | 
					 | 
				
			||||||
		// Always add the default branch
 | 
					 | 
				
			||||||
		log.Debug("loadOneBranch: load default: '%s'", defaultBranch.Name)
 | 
					 | 
				
			||||||
		defaultBranchBranch = loadOneBranch(ctx, defaultBranch, defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo)
 | 
					 | 
				
			||||||
		branches = append(branches, defaultBranchBranch)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if ctx.Repo.CanWrite(unit.TypeCode) {
 | 
					 | 
				
			||||||
		deletedBranches, err := getDeletedBranches(ctx)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			ctx.ServerError("getDeletedBranches", err)
 | 
					 | 
				
			||||||
			return nil, nil, 0
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		branches = append(branches, deletedBranches...)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return defaultBranchBranch, branches, totalNumOfBranches
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches *git_model.ProtectedBranchRules,
 | 
					 | 
				
			||||||
	repoIDToRepo map[int64]*repo_model.Repository,
 | 
					 | 
				
			||||||
	repoIDToGitRepo map[int64]*git.Repository,
 | 
					 | 
				
			||||||
) *Branch {
 | 
					 | 
				
			||||||
	log.Trace("loadOneBranch: '%s'", rawBranch.Name)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	commit, err := rawBranch.GetCommit()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		ctx.ServerError("GetCommit", err)
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	branchName := rawBranch.Name
 | 
					 | 
				
			||||||
	p := protectedBranches.GetFirstMatched(branchName)
 | 
					 | 
				
			||||||
	isProtected := p != nil
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	divergence := &git.DivergeObject{
 | 
					 | 
				
			||||||
		Ahead:  -1,
 | 
					 | 
				
			||||||
		Behind: -1,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if defaultBranch != nil {
 | 
					 | 
				
			||||||
		divergence, err = files_service.CountDivergingCommits(ctx, ctx.Repo.Repository, git.BranchPrefix+branchName)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Error("CountDivergingCommits", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx.Repo.Repository.ID, branchName)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		ctx.ServerError("GetLatestPullRequestByHeadInfo", err)
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	headCommit := commit.ID.String()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	mergeMovedOn := false
 | 
					 | 
				
			||||||
	if pr != nil {
 | 
					 | 
				
			||||||
		pr.HeadRepo = ctx.Repo.Repository
 | 
					 | 
				
			||||||
		if err := pr.LoadIssue(ctx); err != nil {
 | 
					 | 
				
			||||||
			ctx.ServerError("LoadIssue", err)
 | 
					 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
 | 
					 | 
				
			||||||
			pr.BaseRepo = repo
 | 
					 | 
				
			||||||
		} else if err := pr.LoadBaseRepo(ctx); err != nil {
 | 
					 | 
				
			||||||
			ctx.ServerError("LoadBaseRepo", err)
 | 
					 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		pr.Issue.Repo = pr.BaseRepo
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if pr.HasMerged {
 | 
					 | 
				
			||||||
			baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
 | 
					 | 
				
			||||||
			if !ok {
 | 
					 | 
				
			||||||
				baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					ctx.ServerError("OpenRepository", err)
 | 
					 | 
				
			||||||
					return nil
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				defer baseGitRepo.Close()
 | 
					 | 
				
			||||||
				repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
 | 
					 | 
				
			||||||
			if err != nil && !git.IsErrNotExist(err) {
 | 
					 | 
				
			||||||
				ctx.ServerError("GetBranchCommitID", err)
 | 
					 | 
				
			||||||
				return nil
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if err == nil && headCommit != pullCommit {
 | 
					 | 
				
			||||||
				// the head has moved on from the merge - we shouldn't delete
 | 
					 | 
				
			||||||
				mergeMovedOn = true
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	isIncluded := divergence.Ahead == 0 && ctx.Repo.Repository.DefaultBranch != branchName
 | 
					 | 
				
			||||||
	return &Branch{
 | 
					 | 
				
			||||||
		Name:              branchName,
 | 
					 | 
				
			||||||
		Commit:            commit,
 | 
					 | 
				
			||||||
		IsProtected:       isProtected,
 | 
					 | 
				
			||||||
		IsIncluded:        isIncluded,
 | 
					 | 
				
			||||||
		CommitsAhead:      divergence.Ahead,
 | 
					 | 
				
			||||||
		CommitsBehind:     divergence.Behind,
 | 
					 | 
				
			||||||
		LatestPullRequest: pr,
 | 
					 | 
				
			||||||
		MergeMovedOn:      mergeMovedOn,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getDeletedBranches(ctx *context.Context) ([]*Branch, error) {
 | 
					 | 
				
			||||||
	branches := []*Branch{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	deletedBranches, err := git_model.GetDeletedBranches(ctx, ctx.Repo.Repository.ID)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return branches, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for i := range deletedBranches {
 | 
					 | 
				
			||||||
		deletedBranches[i].LoadUser(ctx)
 | 
					 | 
				
			||||||
		branches = append(branches, &Branch{
 | 
					 | 
				
			||||||
			Name:          deletedBranches[i].Name,
 | 
					 | 
				
			||||||
			IsDeleted:     true,
 | 
					 | 
				
			||||||
			DeletedBranch: deletedBranches[i],
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return branches, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// CreateBranch creates new branch in repository
 | 
					// CreateBranch creates new branch in repository
 | 
				
			||||||
func CreateBranch(ctx *context.Context) {
 | 
					func CreateBranch(ctx *context.Context) {
 | 
				
			||||||
	form := web.GetForm(ctx).(*forms.NewBranchForm)
 | 
						form := web.GetForm(ctx).(*forms.NewBranchForm)
 | 
				
			||||||
| 
						 | 
					@ -380,13 +189,13 @@ func CreateBranch(ctx *context.Context) {
 | 
				
			||||||
			ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
 | 
								ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
 | 
							if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
 | 
				
			||||||
			ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName))
 | 
								ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName))
 | 
				
			||||||
			ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
 | 
								ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if models.IsErrBranchNameConflict(err) {
 | 
							if git_model.IsErrBranchNameConflict(err) {
 | 
				
			||||||
			e := err.(models.ErrBranchNameConflict)
 | 
								e := err.(git_model.ErrBranchNameConflict)
 | 
				
			||||||
			ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName))
 | 
								ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName))
 | 
				
			||||||
			ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
 | 
								ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ import (
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
| 
						 | 
					@ -124,9 +125,9 @@ func CherryPickPost(ctx *context.Context) {
 | 
				
			||||||
	// First lets try the simple plain read-tree -m approach
 | 
						// First lets try the simple plain read-tree -m approach
 | 
				
			||||||
	opts.Content = sha
 | 
						opts.Content = sha
 | 
				
			||||||
	if _, err := files.CherryPick(ctx, ctx.Repo.Repository, ctx.Doer, form.Revert, opts); err != nil {
 | 
						if _, err := files.CherryPick(ctx, ctx.Repo.Repository, ctx.Doer, form.Revert, opts); err != nil {
 | 
				
			||||||
		if models.IsErrBranchAlreadyExists(err) {
 | 
							if git_model.IsErrBranchAlreadyExists(err) {
 | 
				
			||||||
			// User has specified a branch that already exists
 | 
								// User has specified a branch that already exists
 | 
				
			||||||
			branchErr := err.(models.ErrBranchAlreadyExists)
 | 
								branchErr := err.(git_model.ErrBranchAlreadyExists)
 | 
				
			||||||
			ctx.Data["Err_NewBranchName"] = true
 | 
								ctx.Data["Err_NewBranchName"] = true
 | 
				
			||||||
			ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
 | 
								ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
| 
						 | 
					@ -161,9 +162,9 @@ func CherryPickPost(ctx *context.Context) {
 | 
				
			||||||
		ctx.Data["FileContent"] = opts.Content
 | 
							ctx.Data["FileContent"] = opts.Content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
 | 
							if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
 | 
				
			||||||
			if models.IsErrBranchAlreadyExists(err) {
 | 
								if git_model.IsErrBranchAlreadyExists(err) {
 | 
				
			||||||
				// User has specified a branch that already exists
 | 
									// User has specified a branch that already exists
 | 
				
			||||||
				branchErr := err.(models.ErrBranchAlreadyExists)
 | 
									branchErr := err.(git_model.ErrBranchAlreadyExists)
 | 
				
			||||||
				ctx.Data["Err_NewBranchName"] = true
 | 
									ctx.Data["Err_NewBranchName"] = true
 | 
				
			||||||
				ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
 | 
									ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,7 @@ import (
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	git_model "code.gitea.io/gitea/models/git"
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
						issues_model "code.gitea.io/gitea/models/issues"
 | 
				
			||||||
	access_model "code.gitea.io/gitea/models/perm/access"
 | 
						access_model "code.gitea.io/gitea/models/perm/access"
 | 
				
			||||||
| 
						 | 
					@ -683,7 +684,13 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer gitRepo.Close()
 | 
						defer gitRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	branches, _, err = gitRepo.GetBranchNames(0, 0)
 | 
						branches, err = git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
 | 
				
			||||||
 | 
							RepoID: repo.ID,
 | 
				
			||||||
 | 
							ListOptions: db.ListOptions{
 | 
				
			||||||
 | 
								ListAll: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							IsDeletedBranch: util.OptionalBoolFalse,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, nil, err
 | 
							return nil, nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -734,7 +741,13 @@ func CompareDiff(ctx *context.Context) {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	headBranches, _, err := ci.HeadGitRepo.GetBranchNames(0, 0)
 | 
						headBranches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
 | 
				
			||||||
 | 
							RepoID: ci.HeadRepo.ID,
 | 
				
			||||||
 | 
							ListOptions: db.ListOptions{
 | 
				
			||||||
 | 
								ListAll: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							IsDeletedBranch: util.OptionalBoolFalse,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetBranches", err)
 | 
							ctx.ServerError("GetBranches", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -327,10 +327,10 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				ctx.Error(http.StatusInternalServerError, err.Error())
 | 
									ctx.Error(http.StatusInternalServerError, err.Error())
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else if models.IsErrBranchAlreadyExists(err) {
 | 
							} else if git_model.IsErrBranchAlreadyExists(err) {
 | 
				
			||||||
			// For when a user specifies a new branch that already exists
 | 
								// For when a user specifies a new branch that already exists
 | 
				
			||||||
			ctx.Data["Err_NewBranchName"] = true
 | 
								ctx.Data["Err_NewBranchName"] = true
 | 
				
			||||||
			if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
 | 
								if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok {
 | 
				
			||||||
				ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
 | 
									ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				ctx.Error(http.StatusInternalServerError, err.Error())
 | 
									ctx.Error(http.StatusInternalServerError, err.Error())
 | 
				
			||||||
| 
						 | 
					@ -529,9 +529,9 @@ func DeleteFilePost(ctx *context.Context) {
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				ctx.Error(http.StatusInternalServerError, err.Error())
 | 
									ctx.Error(http.StatusInternalServerError, err.Error())
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else if models.IsErrBranchAlreadyExists(err) {
 | 
							} else if git_model.IsErrBranchAlreadyExists(err) {
 | 
				
			||||||
			// For when a user specifies a new branch that already exists
 | 
								// For when a user specifies a new branch that already exists
 | 
				
			||||||
			if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
 | 
								if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok {
 | 
				
			||||||
				ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplDeleteFile, &form)
 | 
									ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplDeleteFile, &form)
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				ctx.Error(http.StatusInternalServerError, err.Error())
 | 
									ctx.Error(http.StatusInternalServerError, err.Error())
 | 
				
			||||||
| 
						 | 
					@ -731,10 +731,10 @@ func UploadFilePost(ctx *context.Context) {
 | 
				
			||||||
		} else if git.IsErrBranchNotExist(err) {
 | 
							} else if git.IsErrBranchNotExist(err) {
 | 
				
			||||||
			branchErr := err.(git.ErrBranchNotExist)
 | 
								branchErr := err.(git.ErrBranchNotExist)
 | 
				
			||||||
			ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplUploadFile, &form)
 | 
								ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplUploadFile, &form)
 | 
				
			||||||
		} else if models.IsErrBranchAlreadyExists(err) {
 | 
							} else if git_model.IsErrBranchAlreadyExists(err) {
 | 
				
			||||||
			// For when a user specifies a new branch that already exists
 | 
								// For when a user specifies a new branch that already exists
 | 
				
			||||||
			ctx.Data["Err_NewBranchName"] = true
 | 
								ctx.Data["Err_NewBranchName"] = true
 | 
				
			||||||
			branchErr := err.(models.ErrBranchAlreadyExists)
 | 
								branchErr := err.(git_model.ErrBranchAlreadyExists)
 | 
				
			||||||
			ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form)
 | 
								ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form)
 | 
				
			||||||
		} else if git.IsErrPushOutOfDate(err) {
 | 
							} else if git.IsErrPushOutOfDate(err) {
 | 
				
			||||||
			ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(ctx.Repo.CommitID)+"..."+util.PathEscapeSegments(form.NewBranchName)), tplUploadFile, &form)
 | 
								ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(ctx.Repo.CommitID)+"..."+util.PathEscapeSegments(form.NewBranchName)), tplUploadFile, &form)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -785,7 +785,13 @@ func RetrieveRepoMetas(ctx *context.Context, repo *repo_model.Repository, isPull
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
 | 
						brs, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
 | 
				
			||||||
 | 
							RepoID: ctx.Repo.Repository.ID,
 | 
				
			||||||
 | 
							ListOptions: db.ListOptions{
 | 
				
			||||||
 | 
								ListAll: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							IsDeletedBranch: util.OptionalBoolFalse,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetBranches", err)
 | 
							ctx.ServerError("GetBranches", err)
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@ import (
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
| 
						 | 
					@ -94,9 +95,9 @@ func NewDiffPatchPost(ctx *context.Context) {
 | 
				
			||||||
		Content:      strings.ReplaceAll(form.Content, "\r", ""),
 | 
							Content:      strings.ReplaceAll(form.Content, "\r", ""),
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if models.IsErrBranchAlreadyExists(err) {
 | 
							if git_model.IsErrBranchAlreadyExists(err) {
 | 
				
			||||||
			// User has specified a branch that already exists
 | 
								// User has specified a branch that already exists
 | 
				
			||||||
			branchErr := err.(models.ErrBranchAlreadyExists)
 | 
								branchErr := err.(git_model.ErrBranchAlreadyExists)
 | 
				
			||||||
			ctx.Data["Err_NewBranchName"] = true
 | 
								ctx.Data["Err_NewBranchName"] = true
 | 
				
			||||||
			ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
 | 
								ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1493,7 +1493,7 @@ func UpdatePullRequestTarget(ctx *context.Context) {
 | 
				
			||||||
				"error":      err.Error(),
 | 
									"error":      err.Error(),
 | 
				
			||||||
				"user_error": errorMessage,
 | 
									"user_error": errorMessage,
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		} else if models.IsErrBranchesEqual(err) {
 | 
							} else if git_model.IsErrBranchesEqual(err) {
 | 
				
			||||||
			errorMessage := ctx.Tr("repo.pulls.nothing_to_compare")
 | 
								errorMessage := ctx.Tr("repo.pulls.nothing_to_compare")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ctx.Flash.Error(errorMessage)
 | 
								ctx.Flash.Error(errorMessage)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -286,7 +286,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// FIXME: since we only need to recheck files protected rules, we could improve this
 | 
						// FIXME: since we only need to recheck files protected rules, we could improve this
 | 
				
			||||||
	matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName)
 | 
						matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("FindAllMatchedBranches", err)
 | 
							ctx.ServerError("FindAllMatchedBranches", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,7 +50,7 @@ func ToEmailSearch(email *user_model.SearchEmailResult) *api.Email {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ToBranch convert a git.Commit and git.Branch to an api.Branch
 | 
					// ToBranch convert a git.Commit and git.Branch to an api.Branch
 | 
				
			||||||
func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) {
 | 
					func ToBranch(ctx context.Context, repo *repo_model.Repository, branchName string, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) {
 | 
				
			||||||
	if bp == nil {
 | 
						if bp == nil {
 | 
				
			||||||
		var hasPerm bool
 | 
							var hasPerm bool
 | 
				
			||||||
		var canPush bool
 | 
							var canPush bool
 | 
				
			||||||
| 
						 | 
					@ -65,11 +65,11 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return nil, err
 | 
									return nil, err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			canPush = issues_model.CanMaintainerWriteToBranch(perms, b.Name, user)
 | 
								canPush = issues_model.CanMaintainerWriteToBranch(perms, branchName, user)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return &api.Branch{
 | 
							return &api.Branch{
 | 
				
			||||||
			Name:                b.Name,
 | 
								Name:                branchName,
 | 
				
			||||||
			Commit:              ToPayloadCommit(ctx, repo, c),
 | 
								Commit:              ToPayloadCommit(ctx, repo, c),
 | 
				
			||||||
			Protected:           false,
 | 
								Protected:           false,
 | 
				
			||||||
			RequiredApprovals:   0,
 | 
								RequiredApprovals:   0,
 | 
				
			||||||
| 
						 | 
					@ -81,7 +81,7 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	branch := &api.Branch{
 | 
						branch := &api.Branch{
 | 
				
			||||||
		Name:                b.Name,
 | 
							Name:                branchName,
 | 
				
			||||||
		Commit:              ToPayloadCommit(ctx, repo, c),
 | 
							Commit:              ToPayloadCommit(ctx, repo, c),
 | 
				
			||||||
		Protected:           true,
 | 
							Protected:           true,
 | 
				
			||||||
		RequiredApprovals:   bp.RequiredApprovals,
 | 
							RequiredApprovals:   bp.RequiredApprovals,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -642,7 +642,7 @@ func (g *RepositoryDumper) Finish() error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DumpRepository dump repository according MigrateOptions to a local directory
 | 
					// DumpRepository dump repository according MigrateOptions to a local directory
 | 
				
			||||||
func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.MigrateOptions) error {
 | 
					func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.MigrateOptions) error {
 | 
				
			||||||
	doer, err := user_model.GetAdminUser()
 | 
						doer, err := user_model.GetAdminUser(ctx)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -705,7 +705,7 @@ func updateOptionsUnits(opts *base.MigrateOptions, units []string) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RestoreRepository restore a repository from the disk directory
 | 
					// RestoreRepository restore a repository from the disk directory
 | 
				
			||||||
func RestoreRepository(ctx context.Context, baseDir, ownerName, repoName string, units []string, validation bool) error {
 | 
					func RestoreRepository(ctx context.Context, baseDir, ownerName, repoName string, units []string, validation bool) error {
 | 
				
			||||||
	doer, err := user_model.GetAdminUser()
 | 
						doer, err := user_model.GetAdminUser(ctx)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -170,7 +170,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if branchesEqual {
 | 
						if branchesEqual {
 | 
				
			||||||
		return models.ErrBranchesEqual{
 | 
							return git_model.ErrBranchesEqual{
 | 
				
			||||||
			HeadBranchName: pr.HeadBranch,
 | 
								HeadBranchName: pr.HeadBranch,
 | 
				
			||||||
			BaseBranchName: targetBranch,
 | 
								BaseBranchName: targetBranch,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -338,7 +338,7 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
 | 
				
			||||||
		for _, pr := range prs {
 | 
							for _, pr := range prs {
 | 
				
			||||||
			divergence, err := GetDiverging(ctx, pr)
 | 
								divergence, err := GetDiverging(ctx, pr)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				if models.IsErrBranchDoesNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
 | 
									if git_model.IsErrBranchNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
 | 
				
			||||||
					log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
 | 
										log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					log.Error("GetDiverging: %v", err)
 | 
										log.Error("GetDiverging: %v", err)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,7 @@ import (
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
						issues_model "code.gitea.io/gitea/models/issues"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
| 
						 | 
					@ -181,7 +181,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest)
 | 
				
			||||||
		Run(prCtx.RunOpts()); err != nil {
 | 
							Run(prCtx.RunOpts()); err != nil {
 | 
				
			||||||
		cancel()
 | 
							cancel()
 | 
				
			||||||
		if !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
 | 
							if !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
 | 
				
			||||||
			return nil, nil, models.ErrBranchDoesNotExist{
 | 
								return nil, nil, git_model.ErrBranchNotExist{
 | 
				
			||||||
				BranchName: pr.HeadBranch,
 | 
									BranchName: pr.HeadBranch,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,6 @@ import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
					 | 
				
			||||||
	git_model "code.gitea.io/gitea/models/git"
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
						issues_model "code.gitea.io/gitea/models/issues"
 | 
				
			||||||
	access_model "code.gitea.io/gitea/models/perm/access"
 | 
						access_model "code.gitea.io/gitea/models/perm/access"
 | 
				
			||||||
| 
						 | 
					@ -168,7 +167,7 @@ func GetDiverging(ctx context.Context, pr *issues_model.PullRequest) (*git.Diver
 | 
				
			||||||
	log.Trace("GetDiverging[%-v]: compare commits", pr)
 | 
						log.Trace("GetDiverging[%-v]: compare commits", pr)
 | 
				
			||||||
	prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
 | 
						prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if !models.IsErrBranchDoesNotExist(err) {
 | 
							if !git_model.IsErrBranchNotExist(err) {
 | 
				
			||||||
			log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
 | 
								log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ import (
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/container"
 | 
						"code.gitea.io/gitea/modules/container"
 | 
				
			||||||
| 
						 | 
					@ -146,7 +147,15 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	branches, _, _ := gitRepo.GetBranchNames(0, 0)
 | 
					
 | 
				
			||||||
 | 
						branches, _ := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
 | 
				
			||||||
 | 
							RepoID: repo.ID,
 | 
				
			||||||
 | 
							ListOptions: db.ListOptions{
 | 
				
			||||||
 | 
								ListAll: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							IsDeletedBranch: util.OptionalBoolFalse,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	found := false
 | 
						found := false
 | 
				
			||||||
	hasDefault := false
 | 
						hasDefault := false
 | 
				
			||||||
	hasMaster := false
 | 
						hasMaster := false
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,13 +10,21 @@ import (
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	git_model "code.gitea.io/gitea/models/git"
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
 | 
						issues_model "code.gitea.io/gitea/models/issues"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/graceful"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/notification"
 | 
						"code.gitea.io/gitea/modules/notification"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/queue"
 | 
				
			||||||
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
						repo_module "code.gitea.io/gitea/modules/repository"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
						files_service "code.gitea.io/gitea/services/repository/files"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"xorm.io/builder"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateNewBranch creates a new repository branch
 | 
					// CreateNewBranch creates a new repository branch
 | 
				
			||||||
| 
						 | 
					@ -27,7 +35,7 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) {
 | 
						if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) {
 | 
				
			||||||
		return models.ErrBranchDoesNotExist{
 | 
							return git_model.ErrBranchNotExist{
 | 
				
			||||||
			BranchName: oldBranchName,
 | 
								BranchName: oldBranchName,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -40,16 +48,165 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode
 | 
				
			||||||
		if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
 | 
							if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return fmt.Errorf("Push: %w", err)
 | 
							return fmt.Errorf("push: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetBranches returns branches from the repository, skipping skip initial branches and
 | 
					// Branch contains the branch information
 | 
				
			||||||
// returning at most limit branches, or all branches if limit is 0.
 | 
					type Branch struct {
 | 
				
			||||||
func GetBranches(ctx context.Context, repo *repo_model.Repository, skip, limit int) ([]*git.Branch, int, error) {
 | 
						DBBranch          *git_model.Branch
 | 
				
			||||||
	return git.GetBranchesByPath(ctx, repo.RepoPath(), skip, limit)
 | 
						IsProtected       bool
 | 
				
			||||||
 | 
						IsIncluded        bool
 | 
				
			||||||
 | 
						CommitsAhead      int
 | 
				
			||||||
 | 
						CommitsBehind     int
 | 
				
			||||||
 | 
						LatestPullRequest *issues_model.PullRequest
 | 
				
			||||||
 | 
						MergeMovedOn      bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LoadBranches loads branches from the repository limited by page & pageSize.
 | 
				
			||||||
 | 
					func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch util.OptionalBool, page, pageSize int) (*Branch, []*Branch, int64, error) {
 | 
				
			||||||
 | 
						defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						branchOpts := git_model.FindBranchOptions{
 | 
				
			||||||
 | 
							RepoID:          repo.ID,
 | 
				
			||||||
 | 
							IsDeletedBranch: isDeletedBranch,
 | 
				
			||||||
 | 
							ListOptions: db.ListOptions{
 | 
				
			||||||
 | 
								Page:     page,
 | 
				
			||||||
 | 
								PageSize: pageSize,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						totalNumOfBranches, err := git_model.CountBranches(ctx, branchOpts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						branchOpts.ExcludeBranchNames = []string{repo.DefaultBranch}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dbBranches, err := git_model.FindBranches(ctx, branchOpts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := dbBranches.LoadDeletedBy(ctx); err != nil {
 | 
				
			||||||
 | 
							return nil, nil, 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := dbBranches.LoadPusher(ctx); err != nil {
 | 
				
			||||||
 | 
							return nil, nil, 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rules, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						repoIDToRepo := map[int64]*repo_model.Repository{}
 | 
				
			||||||
 | 
						repoIDToRepo[repo.ID] = repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						repoIDToGitRepo := map[int64]*git.Repository{}
 | 
				
			||||||
 | 
						repoIDToGitRepo[repo.ID] = gitRepo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						branches := make([]*Branch, 0, len(dbBranches))
 | 
				
			||||||
 | 
						for i := range dbBranches {
 | 
				
			||||||
 | 
							branch, err := loadOneBranch(ctx, repo, dbBranches[i], &rules, repoIDToRepo, repoIDToGitRepo)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							branches = append(branches, branch)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Always add the default branch
 | 
				
			||||||
 | 
						log.Debug("loadOneBranch: load default: '%s'", defaultDBBranch.Name)
 | 
				
			||||||
 | 
						defaultBranch, err := loadOneBranch(ctx, repo, defaultDBBranch, &rules, repoIDToRepo, repoIDToGitRepo)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return defaultBranch, branches, totalNumOfBranches, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *git_model.Branch, protectedBranches *git_model.ProtectedBranchRules,
 | 
				
			||||||
 | 
						repoIDToRepo map[int64]*repo_model.Repository,
 | 
				
			||||||
 | 
						repoIDToGitRepo map[int64]*git.Repository,
 | 
				
			||||||
 | 
					) (*Branch, error) {
 | 
				
			||||||
 | 
						log.Trace("loadOneBranch: '%s'", dbBranch.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						branchName := dbBranch.Name
 | 
				
			||||||
 | 
						p := protectedBranches.GetFirstMatched(branchName)
 | 
				
			||||||
 | 
						isProtected := p != nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						divergence := &git.DivergeObject{
 | 
				
			||||||
 | 
							Ahead:  -1,
 | 
				
			||||||
 | 
							Behind: -1,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// it's not default branch
 | 
				
			||||||
 | 
						if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted {
 | 
				
			||||||
 | 
							var err error
 | 
				
			||||||
 | 
							divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("CountDivergingCommits: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pr, err := issues_model.GetLatestPullRequestByHeadInfo(repo.ID, branchName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						headCommit := dbBranch.CommitID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mergeMovedOn := false
 | 
				
			||||||
 | 
						if pr != nil {
 | 
				
			||||||
 | 
							pr.HeadRepo = repo
 | 
				
			||||||
 | 
							if err := pr.LoadIssue(ctx); err != nil {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("LoadIssue: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
 | 
				
			||||||
 | 
								pr.BaseRepo = repo
 | 
				
			||||||
 | 
							} else if err := pr.LoadBaseRepo(ctx); err != nil {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("LoadBaseRepo: %v", err)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							pr.Issue.Repo = pr.BaseRepo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if pr.HasMerged {
 | 
				
			||||||
 | 
								baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
 | 
				
			||||||
 | 
								if !ok {
 | 
				
			||||||
 | 
									baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, fmt.Errorf("OpenRepository: %v", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									defer baseGitRepo.Close()
 | 
				
			||||||
 | 
									repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
 | 
				
			||||||
 | 
								if err != nil && !git.IsErrNotExist(err) {
 | 
				
			||||||
 | 
									return nil, fmt.Errorf("GetBranchCommitID: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err == nil && headCommit != pullCommit {
 | 
				
			||||||
 | 
									// the head has moved on from the merge - we shouldn't delete
 | 
				
			||||||
 | 
									mergeMovedOn = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						isIncluded := divergence.Ahead == 0 && repo.DefaultBranch != branchName
 | 
				
			||||||
 | 
						return &Branch{
 | 
				
			||||||
 | 
							DBBranch:          dbBranch,
 | 
				
			||||||
 | 
							IsProtected:       isProtected,
 | 
				
			||||||
 | 
							IsIncluded:        isIncluded,
 | 
				
			||||||
 | 
							CommitsAhead:      divergence.Ahead,
 | 
				
			||||||
 | 
							CommitsBehind:     divergence.Behind,
 | 
				
			||||||
 | 
							LatestPullRequest: pr,
 | 
				
			||||||
 | 
							MergeMovedOn:      mergeMovedOn,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetBranchCommitID(ctx context.Context, repo *repo_model.Repository, branch string) (string, error) {
 | 
					func GetBranchCommitID(ctx context.Context, repo *repo_model.Repository, branch string) (string, error) {
 | 
				
			||||||
| 
						 | 
					@ -62,17 +219,17 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri
 | 
				
			||||||
		branchRefName := strings.TrimPrefix(refName, git.BranchPrefix)
 | 
							branchRefName := strings.TrimPrefix(refName, git.BranchPrefix)
 | 
				
			||||||
		switch {
 | 
							switch {
 | 
				
			||||||
		case branchRefName == name:
 | 
							case branchRefName == name:
 | 
				
			||||||
			return models.ErrBranchAlreadyExists{
 | 
								return git_model.ErrBranchAlreadyExists{
 | 
				
			||||||
				BranchName: name,
 | 
									BranchName: name,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		// If branchRefName like a/b but we want to create a branch named a then we have a conflict
 | 
							// If branchRefName like a/b but we want to create a branch named a then we have a conflict
 | 
				
			||||||
		case strings.HasPrefix(branchRefName, name+"/"):
 | 
							case strings.HasPrefix(branchRefName, name+"/"):
 | 
				
			||||||
			return models.ErrBranchNameConflict{
 | 
								return git_model.ErrBranchNameConflict{
 | 
				
			||||||
				BranchName: branchRefName,
 | 
									BranchName: branchRefName,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			// Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict
 | 
								// Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict
 | 
				
			||||||
		case strings.HasPrefix(name, branchRefName+"/"):
 | 
							case strings.HasPrefix(name, branchRefName+"/"):
 | 
				
			||||||
			return models.ErrBranchNameConflict{
 | 
								return git_model.ErrBranchNameConflict{
 | 
				
			||||||
				BranchName: branchRefName,
 | 
									BranchName: branchRefName,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		case refName == git.TagPrefix+name:
 | 
							case refName == git.TagPrefix+name:
 | 
				
			||||||
| 
						 | 
					@ -101,7 +258,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo
 | 
				
			||||||
		if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
 | 
							if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return fmt.Errorf("Push: %w", err)
 | 
							return fmt.Errorf("push: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
| 
						 | 
					@ -169,13 +326,28 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
 | 
				
			||||||
		return git_model.ErrBranchIsProtected
 | 
							return git_model.ErrBranchIsProtected
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("GetBranch: %vc", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if rawBranch.IsDeleted {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	commit, err := gitRepo.GetBranchCommit(branchName)
 | 
						commit, err := gitRepo.GetBranchCommit(branchName)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
 | 
						if err := db.WithTx(ctx, func(ctx context.Context) error {
 | 
				
			||||||
		Force: true,
 | 
							if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
 | 
				
			||||||
 | 
								Force: true,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -196,3 +368,45 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type BranchSyncOptions struct {
 | 
				
			||||||
 | 
						RepoID int64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// branchSyncQueue represents a queue to handle branch sync jobs.
 | 
				
			||||||
 | 
					var branchSyncQueue *queue.WorkerPoolQueue[*BranchSyncOptions]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions {
 | 
				
			||||||
 | 
						for _, opts := range items {
 | 
				
			||||||
 | 
							_, err := repo_module.SyncRepoBranches(graceful.GetManager().ShutdownContext(), opts.RepoID, 0)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error("syncRepoBranches [%d] failed: %v", opts.RepoID, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func addRepoToBranchSyncQueue(repoID, doerID int64) error {
 | 
				
			||||||
 | 
						return branchSyncQueue.Push(&BranchSyncOptions{
 | 
				
			||||||
 | 
							RepoID: repoID,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func initBranchSyncQueue(ctx context.Context) error {
 | 
				
			||||||
 | 
						branchSyncQueue = queue.CreateUniqueQueue(ctx, "branch_sync", handlerBranchSync)
 | 
				
			||||||
 | 
						if branchSyncQueue == nil {
 | 
				
			||||||
 | 
							return errors.New("unable to create branch_sync queue")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						go graceful.GetManager().RunWithCancel(branchSyncQueue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error {
 | 
				
			||||||
 | 
						if err := db.Iterate(ctx, builder.Eq{"is_empty": false}, func(ctx context.Context, repo *repo_model.Repository) error {
 | 
				
			||||||
 | 
							return addRepoToBranchSyncQueue(repo.ID, doerID)
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("run sync all branches failed: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,7 +58,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode
 | 
				
			||||||
	if opts.NewBranch != opts.OldBranch {
 | 
						if opts.NewBranch != opts.OldBranch {
 | 
				
			||||||
		existingBranch, err := gitRepo.GetBranch(opts.NewBranch)
 | 
							existingBranch, err := gitRepo.GetBranch(opts.NewBranch)
 | 
				
			||||||
		if existingBranch != nil {
 | 
							if existingBranch != nil {
 | 
				
			||||||
			return models.ErrBranchAlreadyExists{
 | 
								return git_model.ErrBranchAlreadyExists{
 | 
				
			||||||
				BranchName: opts.NewBranch,
 | 
									BranchName: opts.NewBranch,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -197,7 +197,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
 | 
				
			||||||
	if opts.NewBranch != opts.OldBranch {
 | 
						if opts.NewBranch != opts.OldBranch {
 | 
				
			||||||
		existingBranch, err := gitRepo.GetBranch(opts.NewBranch)
 | 
							existingBranch, err := gitRepo.GetBranch(opts.NewBranch)
 | 
				
			||||||
		if existingBranch != nil {
 | 
							if existingBranch != nil {
 | 
				
			||||||
			return nil, models.ErrBranchAlreadyExists{
 | 
								return nil, git_model.ErrBranchAlreadyExists{
 | 
				
			||||||
				BranchName: opts.NewBranch,
 | 
									BranchName: opts.NewBranch,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -157,7 +157,15 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
 | 
				
			||||||
		if err = repo_module.CreateDelegateHooks(repoPath); err != nil {
 | 
							if err = repo_module.CreateDelegateHooks(repoPath); err != nil {
 | 
				
			||||||
			return fmt.Errorf("createDelegateHooks: %w", err)
 | 
								return fmt.Errorf("createDelegateHooks: %w", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return nil
 | 
					
 | 
				
			||||||
 | 
							gitRepo, err := git.OpenRepository(txCtx, repo.RepoPath())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("OpenRepository: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer gitRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_, err = repo_module.SyncRepoBranchesWithRepo(txCtx, repo, gitRepo, doer.ID)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	needsRollbackInPanic = false
 | 
						needsRollbackInPanic = false
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -93,7 +93,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
 | 
				
			||||||
	defer gitRepo.Close()
 | 
						defer gitRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
 | 
						if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
 | 
				
			||||||
		log.Error("Failed to update size for repository: %v", err)
 | 
							return fmt.Errorf("Failed to update size for repository: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	addTags := make([]string, 0, len(optsList))
 | 
						addTags := make([]string, 0, len(optsList))
 | 
				
			||||||
| 
						 | 
					@ -259,8 +259,8 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				notification.NotifyPushCommits(ctx, pusher, repo, opts, commits)
 | 
									notification.NotifyPushCommits(ctx, pusher, repo, opts, commits)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if err = git_model.RemoveDeletedBranchByName(ctx, repo.ID, branch); err != nil {
 | 
									if err = git_model.UpdateBranch(ctx, repo.ID, branch, newCommit.ID.String(), newCommit.CommitMessage, opts.PusherID, newCommit.Committer.When); err != nil {
 | 
				
			||||||
					log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err)
 | 
										return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Cache for big repository
 | 
									// Cache for big repository
 | 
				
			||||||
| 
						 | 
					@ -273,8 +273,9 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
 | 
				
			||||||
					// close all related pulls
 | 
										// close all related pulls
 | 
				
			||||||
					log.Error("close related pull request failed: %v", err)
 | 
										log.Error("close related pull request failed: %v", err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, opts.OldCommitID, pusher.ID); err != nil {
 | 
					
 | 
				
			||||||
					log.Warn("AddDeletedBranch: %v", err)
 | 
									if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, pusher.ID); err != nil {
 | 
				
			||||||
 | 
										return fmt.Errorf("AddDeletedBranch %s:%s failed: %v", repo.FullName(), branch, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@ import (
 | 
				
			||||||
	system_model "code.gitea.io/gitea/models/system"
 | 
						system_model "code.gitea.io/gitea/models/system"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/graceful"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/notification"
 | 
						"code.gitea.io/gitea/modules/notification"
 | 
				
			||||||
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
						repo_module "code.gitea.io/gitea/modules/repository"
 | 
				
			||||||
| 
						 | 
					@ -100,7 +101,10 @@ func Init() error {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath)
 | 
						system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath)
 | 
				
			||||||
	system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath())
 | 
						system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath())
 | 
				
			||||||
	return initPushQueue()
 | 
						if err := initPushQueue(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return initBranchSyncQueue(graceful.GetManager().ShutdownContext())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateRepository updates a repository
 | 
					// UpdateRepository updates a repository
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,6 +61,10 @@
 | 
				
			||||||
							<td>{{.locale.Tr "admin.dashboard.delete_generated_repository_avatars"}}</td>
 | 
												<td>{{.locale.Tr "admin.dashboard.delete_generated_repository_avatars"}}</td>
 | 
				
			||||||
							<td class="text right"><button type="submit" class="ui green button" name="op" value="delete_generated_repository_avatars">{{svg "octicon-play"}} {{.locale.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
												<td class="text right"><button type="submit" class="ui green button" name="op" value="delete_generated_repository_avatars">{{svg "octicon-play"}} {{.locale.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
						</tr>
 | 
											</tr>
 | 
				
			||||||
 | 
											<tr>
 | 
				
			||||||
 | 
												<td>{{.locale.Tr "admin.dashboard.sync_repo_branches"}}</td>
 | 
				
			||||||
 | 
												<td class="text right"><button type="submit" class="ui green button" name="op" value="sync_repo_branches">{{svg "octicon-play"}} {{.locale.Tr "admin.dashboard.operation_run"}}</button></td>
 | 
				
			||||||
 | 
											</tr>
 | 
				
			||||||
					</tbody>
 | 
										</tbody>
 | 
				
			||||||
				</table>
 | 
									</table>
 | 
				
			||||||
			</form>
 | 
								</form>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,29 +22,29 @@
 | 
				
			||||||
								{{if .DefaultBranchBranch.IsProtected}}
 | 
													{{if .DefaultBranchBranch.IsProtected}}
 | 
				
			||||||
									{{svg "octicon-shield-lock"}}
 | 
														{{svg "octicon-shield-lock"}}
 | 
				
			||||||
								{{end}}
 | 
													{{end}}
 | 
				
			||||||
								<a href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranch}}">{{.DefaultBranch}}</a>
 | 
													<a href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{.DefaultBranchBranch.DBBranch.Name}}</a>
 | 
				
			||||||
								<p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.Commit.ID.String}}">{{ShortSha .DefaultBranchBranch.Commit.ID.String}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.Commit.CommitMessage .RepoLink .Repository.ComposeMetas}}</span> · {{.locale.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.Commit.Committer.When .locale}}</p>
 | 
													<p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.DBBranch.CommitMessage .RepoLink .Repository.ComposeMetas}}</span> · {{.locale.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.DBBranch.CommitTime.AsTime .locale}}{{if .DefaultBranchBranch.DBBranch.Pusher}}  {{template "shared/user/avatarlink" dict "Context" $.Context "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
 | 
				
			||||||
							</td>
 | 
												</td>
 | 
				
			||||||
							<td class="right aligned overflow-visible">
 | 
												<td class="right aligned overflow-visible">
 | 
				
			||||||
								{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
 | 
													{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
 | 
				
			||||||
									<button class="btn interact-bg show-create-branch-modal gt-p-3"
 | 
														<button class="btn interact-bg show-create-branch-modal gt-p-3"
 | 
				
			||||||
										data-modal="#create-branch-modal"
 | 
															data-modal="#create-branch-modal"
 | 
				
			||||||
										data-branch-from="{{$.DefaultBranch}}"
 | 
															data-branch-from="{{$.DefaultBranchBranch}}"
 | 
				
			||||||
										data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranch}}"
 | 
															data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}"
 | 
				
			||||||
										data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" ($.DefaultBranch)}}"
 | 
															data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" ($.DefaultBranchBranch.DBBranch.Name)}}"
 | 
				
			||||||
									>
 | 
														>
 | 
				
			||||||
										{{svg "octicon-git-branch"}}
 | 
															{{svg "octicon-git-branch"}}
 | 
				
			||||||
									</button>
 | 
														</button>
 | 
				
			||||||
								{{end}}
 | 
													{{end}}
 | 
				
			||||||
								{{if .EnableFeed}}
 | 
													{{if .EnableFeed}}
 | 
				
			||||||
									<a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranch}}">{{svg "octicon-rss"}}</a>
 | 
														<a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{svg "octicon-rss"}}</a>
 | 
				
			||||||
								{{end}}
 | 
													{{end}}
 | 
				
			||||||
								{{if not $.DisableDownloadSourceArchives}}
 | 
													{{if not $.DisableDownloadSourceArchives}}
 | 
				
			||||||
									<div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranch)}}">
 | 
														<div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranchBranch.DBBranch.Name)}}">
 | 
				
			||||||
										{{svg "octicon-download"}}
 | 
															{{svg "octicon-download"}}
 | 
				
			||||||
										<div class="menu">
 | 
															<div class="menu">
 | 
				
			||||||
											<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.zip" rel="nofollow">{{svg "octicon-file-zip"}} ZIP</a>
 | 
																<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}} ZIP</a>
 | 
				
			||||||
											<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}} TAR.GZ</a>
 | 
																<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}} TAR.GZ</a>
 | 
				
			||||||
										</div>
 | 
															</div>
 | 
				
			||||||
									</div>
 | 
														</div>
 | 
				
			||||||
								{{end}}
 | 
													{{end}}
 | 
				
			||||||
| 
						 | 
					@ -52,8 +52,8 @@
 | 
				
			||||||
									<button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
 | 
														<button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
 | 
				
			||||||
										data-is-default-branch="true"
 | 
															data-is-default-branch="true"
 | 
				
			||||||
										data-modal="#rename-branch-modal"
 | 
															data-modal="#rename-branch-modal"
 | 
				
			||||||
										data-old-branch-name="{{$.DefaultBranch}}"
 | 
															data-old-branch-name="{{$.DefaultBranchBranch}}"
 | 
				
			||||||
										data-tooltip-content="{{$.locale.Tr "repo.branch.rename" ($.DefaultBranch)}}"
 | 
															data-tooltip-content="{{$.locale.Tr "repo.branch.rename" ($.DefaultBranchBranch.DBBranch.Name)}}"
 | 
				
			||||||
									>
 | 
														>
 | 
				
			||||||
										{{svg "octicon-pencil"}}
 | 
															{{svg "octicon-pencil"}}
 | 
				
			||||||
									</button>
 | 
														</button>
 | 
				
			||||||
| 
						 | 
					@ -65,7 +65,7 @@
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		{{end}}
 | 
							{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		{{if gt (len .Branches) 1}}
 | 
							{{if .Branches}}
 | 
				
			||||||
			<h4 class="ui top attached header">
 | 
								<h4 class="ui top attached header">
 | 
				
			||||||
				{{.locale.Tr "repo.branches"}}
 | 
									{{.locale.Tr "repo.branches"}}
 | 
				
			||||||
			</h4>
 | 
								</h4>
 | 
				
			||||||
| 
						 | 
					@ -73,112 +73,110 @@
 | 
				
			||||||
				<table class="ui very basic striped fixed table single line">
 | 
									<table class="ui very basic striped fixed table single line">
 | 
				
			||||||
					<tbody>
 | 
										<tbody>
 | 
				
			||||||
						{{range .Branches}}
 | 
											{{range .Branches}}
 | 
				
			||||||
							{{if ne .Name $.DefaultBranch}}
 | 
												<tr>
 | 
				
			||||||
								<tr>
 | 
													<td class="eight wide">
 | 
				
			||||||
									<td class="six wide">
 | 
													{{if .DBBranch.IsDeleted}}
 | 
				
			||||||
									{{if .IsDeleted}}
 | 
														<s><a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a></s>
 | 
				
			||||||
										<s><a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .Name}}">{{.Name}}</a></s>
 | 
														<p class="info">{{$.locale.Tr "repo.branch.deleted_by" .DBBranch.DeletedBy.Name}} {{TimeSinceUnix .DBBranch.DeletedUnix $.locale}}</p>
 | 
				
			||||||
										<p class="info">{{$.locale.Tr "repo.branch.deleted_by" .DeletedBranch.DeletedBy.Name}} {{TimeSinceUnix .DeletedBranch.DeletedUnix $.locale}}</p>
 | 
													{{else}}
 | 
				
			||||||
									{{else}}
 | 
														{{if .IsProtected}}
 | 
				
			||||||
										{{if .IsProtected}}
 | 
															{{svg "octicon-shield-lock"}}
 | 
				
			||||||
											{{svg "octicon-shield-lock"}}
 | 
					 | 
				
			||||||
										{{end}}
 | 
					 | 
				
			||||||
										<a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .Name}}">{{.Name}}</a>
 | 
					 | 
				
			||||||
										<p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .Commit.ID.String}}">{{ShortSha .Commit.ID.String}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .Commit.CommitMessage $.RepoLink $.Repository.ComposeMetas}}</span> · {{$.locale.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.locale}}</p>
 | 
					 | 
				
			||||||
									{{end}}
 | 
														{{end}}
 | 
				
			||||||
									</td>
 | 
														<a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a>
 | 
				
			||||||
									<td class="three wide ui">
 | 
														<p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DBBranch.CommitMessage $.RepoLink $.Repository.ComposeMetas}}</span> · {{$.locale.Tr "org.repo_updated"}} {{TimeSince .DBBranch.CommitTime.AsTime $.locale}}{{if .DBBranch.Pusher}}  {{template "shared/user/avatarlink" dict "Context" $.Context "user" .DBBranch.Pusher}}  {{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
 | 
				
			||||||
										{{if and (not .IsDeleted) $.DefaultBranchBranch}}
 | 
													{{end}}
 | 
				
			||||||
										<div class="commit-divergence">
 | 
													</td>
 | 
				
			||||||
											<div class="bar-group">
 | 
													<td class="two wide ui">
 | 
				
			||||||
												<div class="count count-behind">{{.CommitsBehind}}</div>
 | 
														{{if and (not .DBBranch.IsDeleted) $.DefaultBranchBranch}}
 | 
				
			||||||
												{{/* old code bears 0/0.0 = NaN output, so it might output invalid "width: NaNpx", it just works and doesn't caues any problem. */}}
 | 
														<div class="commit-divergence">
 | 
				
			||||||
												<div class="bar bar-behind" style="width: {{Eval 100 "*" .CommitsBehind "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div>
 | 
															<div class="bar-group">
 | 
				
			||||||
											</div>
 | 
																<div class="count count-behind">{{.CommitsBehind}}</div>
 | 
				
			||||||
											<div class="bar-group">
 | 
																{{/* old code bears 0/0.0 = NaN output, so it might output invalid "width: NaNpx", it just works and doesn't caues any problem. */}}
 | 
				
			||||||
												<div class="count count-ahead">{{.CommitsAhead}}</div>
 | 
																<div class="bar bar-behind" style="width: {{Eval 100 "*" .CommitsBehind "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div>
 | 
				
			||||||
												<div class="bar bar-ahead" style="width: {{Eval 100 "*" .CommitsAhead "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div>
 | 
															</div>
 | 
				
			||||||
 | 
															<div class="bar-group">
 | 
				
			||||||
 | 
																<div class="count count-ahead">{{.CommitsAhead}}</div>
 | 
				
			||||||
 | 
																<div class="bar bar-ahead" style="width: {{Eval 100 "*" .CommitsAhead "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div>
 | 
				
			||||||
 | 
															</div>
 | 
				
			||||||
 | 
														</div>
 | 
				
			||||||
 | 
														{{end}}
 | 
				
			||||||
 | 
													</td>
 | 
				
			||||||
 | 
													<td class="two wide right aligned">
 | 
				
			||||||
 | 
														{{if not .LatestPullRequest}}
 | 
				
			||||||
 | 
															{{if .IsIncluded}}
 | 
				
			||||||
 | 
																<span class="ui orange large label" data-tooltip-content="{{$.locale.Tr "repo.branch.included_desc"}}">
 | 
				
			||||||
 | 
																	{{svg "octicon-git-pull-request"}} {{$.locale.Tr "repo.branch.included"}}
 | 
				
			||||||
 | 
																</span>
 | 
				
			||||||
 | 
															{{else if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
 | 
				
			||||||
 | 
															<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .DBBranch.Name}}">
 | 
				
			||||||
 | 
																<button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button>
 | 
				
			||||||
 | 
															</a>
 | 
				
			||||||
 | 
															{{end}}
 | 
				
			||||||
 | 
														{{else if and .LatestPullRequest.HasMerged .MergeMovedOn}}
 | 
				
			||||||
 | 
															{{if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
 | 
				
			||||||
 | 
															<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | PathEscapeSegments}}">
 | 
				
			||||||
 | 
																<button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button>
 | 
				
			||||||
 | 
															</a>
 | 
				
			||||||
 | 
															{{end}}
 | 
				
			||||||
 | 
														{{else}}
 | 
				
			||||||
 | 
															<a href="{{.LatestPullRequest.Issue.Link}}" class="gt-vm ref-issue">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
 | 
				
			||||||
 | 
															{{if .LatestPullRequest.HasMerged}}
 | 
				
			||||||
 | 
																<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label purple large label gt-vm">{{svg "octicon-git-merge" 16 "gt-mr-2"}}{{$.locale.Tr "repo.pulls.merged"}}</a>
 | 
				
			||||||
 | 
															{{else if .LatestPullRequest.Issue.IsClosed}}
 | 
				
			||||||
 | 
																<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label red large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.closed_title"}}</a>
 | 
				
			||||||
 | 
															{{else}}
 | 
				
			||||||
 | 
																<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label green large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.open_title"}}</a>
 | 
				
			||||||
 | 
															{{end}}
 | 
				
			||||||
 | 
														{{end}}
 | 
				
			||||||
 | 
													</td>
 | 
				
			||||||
 | 
													<td class="three wide right aligned overflow-visible">
 | 
				
			||||||
 | 
														{{if and $.IsWriter (not $.Repository.IsArchived) (not .DBBranch.IsDeleted)}}
 | 
				
			||||||
 | 
															<button class="btn interact-bg gt-p-3 show-modal show-create-branch-modal"
 | 
				
			||||||
 | 
																data-branch-from="{{.DBBranch.Name}}"
 | 
				
			||||||
 | 
																data-branch-from-urlcomponent="{{PathEscapeSegments .DBBranch.Name}}"
 | 
				
			||||||
 | 
																data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" .DBBranch.Name}}"
 | 
				
			||||||
 | 
																data-modal="#create-branch-modal" data-name="{{.DBBranch.Name}}"
 | 
				
			||||||
 | 
															>
 | 
				
			||||||
 | 
																{{svg "octicon-git-branch"}}
 | 
				
			||||||
 | 
															</button>
 | 
				
			||||||
 | 
														{{end}}
 | 
				
			||||||
 | 
														{{if $.EnableFeed}}
 | 
				
			||||||
 | 
															<a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DBBranch.Name}}">{{svg "octicon-rss"}}</a>
 | 
				
			||||||
 | 
														{{end}}
 | 
				
			||||||
 | 
														{{if and (not .DBBranch.IsDeleted) (not $.DisableDownloadSourceArchives)}}
 | 
				
			||||||
 | 
															<div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.DBBranch.Name)}}">
 | 
				
			||||||
 | 
																{{svg "octicon-download"}}
 | 
				
			||||||
 | 
																<div class="menu">
 | 
				
			||||||
 | 
																	<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .DBBranch.Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}} ZIP</a>
 | 
				
			||||||
 | 
																	<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .DBBranch.Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}} TAR.GZ</a>
 | 
				
			||||||
											</div>
 | 
																</div>
 | 
				
			||||||
										</div>
 | 
															</div>
 | 
				
			||||||
										{{end}}
 | 
														{{end}}
 | 
				
			||||||
									</td>
 | 
														{{if and $.IsWriter (not $.Repository.IsArchived) (not .DBBranch.IsDeleted) (not $.IsMirror)}}
 | 
				
			||||||
									<td class="three wide right aligned">
 | 
															<button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
 | 
				
			||||||
										{{if not .LatestPullRequest}}
 | 
																data-is-default-branch="false"
 | 
				
			||||||
											{{if .IsIncluded}}
 | 
																data-old-branch-name="{{.DBBranch.Name}}"
 | 
				
			||||||
												<span class="ui orange large label" data-tooltip-content="{{$.locale.Tr "repo.branch.included_desc"}}">
 | 
																data-modal="#rename-branch-modal"
 | 
				
			||||||
													{{svg "octicon-git-pull-request"}} {{$.locale.Tr "repo.branch.included"}}
 | 
																data-tooltip-content="{{$.locale.Tr "repo.branch.rename" (.DBBranch.Name)}}"
 | 
				
			||||||
 | 
															>
 | 
				
			||||||
 | 
																{{svg "octicon-pencil"}}
 | 
				
			||||||
 | 
															</button>
 | 
				
			||||||
 | 
														{{end}}
 | 
				
			||||||
 | 
														{{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}}
 | 
				
			||||||
 | 
															{{if .DBBranch.IsDeleted}}
 | 
				
			||||||
 | 
																<button class="btn interact-bg gt-p-3 link-action restore-branch-button" data-url="{{$.Link}}/restore?branch_id={{.DBBranch.ID}}&name={{.DBBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.restore" (.DBBranch.Name)}}">
 | 
				
			||||||
 | 
																	<span class="text blue">
 | 
				
			||||||
 | 
																		{{svg "octicon-reply"}}
 | 
				
			||||||
												</span>
 | 
																	</span>
 | 
				
			||||||
											{{else if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
 | 
																</button>
 | 
				
			||||||
											<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .Name}}">
 | 
					 | 
				
			||||||
												<button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button>
 | 
					 | 
				
			||||||
											</a>
 | 
					 | 
				
			||||||
											{{end}}
 | 
					 | 
				
			||||||
										{{else if and .LatestPullRequest.HasMerged .MergeMovedOn}}
 | 
					 | 
				
			||||||
											{{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
 | 
					 | 
				
			||||||
											<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | PathEscapeSegments}}">
 | 
					 | 
				
			||||||
												<button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button>
 | 
					 | 
				
			||||||
											</a>
 | 
					 | 
				
			||||||
											{{end}}
 | 
					 | 
				
			||||||
										{{else}}
 | 
															{{else}}
 | 
				
			||||||
											<a href="{{.LatestPullRequest.Issue.Link}}" class="gt-vm ref-issue">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
 | 
																<button class="btn interact-bg gt-p-3 delete-button delete-branch-button" data-url="{{$.Link}}/delete?name={{.DBBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.delete" (.DBBranch.Name)}}" data-name="{{.DBBranch.Name}}">
 | 
				
			||||||
											{{if .LatestPullRequest.HasMerged}}
 | 
																	{{svg "octicon-trash"}}
 | 
				
			||||||
												<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label purple large label gt-vm">{{svg "octicon-git-merge" 16 "gt-mr-2"}}{{$.locale.Tr "repo.pulls.merged"}}</a>
 | 
					 | 
				
			||||||
											{{else if .LatestPullRequest.Issue.IsClosed}}
 | 
					 | 
				
			||||||
												<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label red large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.closed_title"}}</a>
 | 
					 | 
				
			||||||
											{{else}}
 | 
					 | 
				
			||||||
												<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label green large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.open_title"}}</a>
 | 
					 | 
				
			||||||
											{{end}}
 | 
					 | 
				
			||||||
										{{end}}
 | 
					 | 
				
			||||||
									</td>
 | 
					 | 
				
			||||||
									<td class="three wide right aligned overflow-visible">
 | 
					 | 
				
			||||||
										{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
 | 
					 | 
				
			||||||
											<button class="btn interact-bg gt-p-3 show-modal show-create-branch-modal"
 | 
					 | 
				
			||||||
												data-branch-from="{{.Name}}"
 | 
					 | 
				
			||||||
												data-branch-from-urlcomponent="{{PathEscapeSegments .Name}}"
 | 
					 | 
				
			||||||
												data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" .Name}}"
 | 
					 | 
				
			||||||
												data-modal="#create-branch-modal" data-name="{{.Name}}"
 | 
					 | 
				
			||||||
											>
 | 
					 | 
				
			||||||
												{{svg "octicon-git-branch"}}
 | 
					 | 
				
			||||||
											</button>
 | 
																</button>
 | 
				
			||||||
										{{end}}
 | 
															{{end}}
 | 
				
			||||||
										{{if $.EnableFeed}}
 | 
														{{end}}
 | 
				
			||||||
											<a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .Name}}">{{svg "octicon-rss"}}</a>
 | 
													</td>
 | 
				
			||||||
										{{end}}
 | 
												</tr>
 | 
				
			||||||
										{{if and (not .IsDeleted) (not $.DisableDownloadSourceArchives)}}
 | 
					 | 
				
			||||||
											<div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.Name)}}">
 | 
					 | 
				
			||||||
												{{svg "octicon-download"}}
 | 
					 | 
				
			||||||
												<div class="menu">
 | 
					 | 
				
			||||||
													<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}} ZIP</a>
 | 
					 | 
				
			||||||
													<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}} TAR.GZ</a>
 | 
					 | 
				
			||||||
												</div>
 | 
					 | 
				
			||||||
											</div>
 | 
					 | 
				
			||||||
										{{end}}
 | 
					 | 
				
			||||||
										{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted) (not $.IsMirror)}}
 | 
					 | 
				
			||||||
											<button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
 | 
					 | 
				
			||||||
												data-is-default-branch="false"
 | 
					 | 
				
			||||||
												data-old-branch-name="{{.Name}}"
 | 
					 | 
				
			||||||
												data-modal="#rename-branch-modal"
 | 
					 | 
				
			||||||
												data-tooltip-content="{{$.locale.Tr "repo.branch.rename" (.Name)}}"
 | 
					 | 
				
			||||||
											>
 | 
					 | 
				
			||||||
												{{svg "octicon-pencil"}}
 | 
					 | 
				
			||||||
											</button>
 | 
					 | 
				
			||||||
										{{end}}
 | 
					 | 
				
			||||||
										{{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}}
 | 
					 | 
				
			||||||
											{{if .IsDeleted}}
 | 
					 | 
				
			||||||
												<button class="btn interact-bg gt-p-3 link-action restore-branch-button" data-url="{{$.Link}}/restore?branch_id={{.DeletedBranch.ID}}&name={{.DeletedBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.restore" (.Name)}}">
 | 
					 | 
				
			||||||
													<span class="text blue">
 | 
					 | 
				
			||||||
														{{svg "octicon-reply"}}
 | 
					 | 
				
			||||||
													</span>
 | 
					 | 
				
			||||||
												</button>
 | 
					 | 
				
			||||||
											{{else}}
 | 
					 | 
				
			||||||
												<button class="btn interact-bg gt-p-3 delete-button delete-branch-button" data-url="{{$.Link}}/delete?name={{.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.delete" (.Name)}}" data-name="{{.Name}}">
 | 
					 | 
				
			||||||
													{{svg "octicon-trash"}}
 | 
					 | 
				
			||||||
												</button>
 | 
					 | 
				
			||||||
											{{end}}
 | 
					 | 
				
			||||||
										{{end}}
 | 
					 | 
				
			||||||
									</td>
 | 
					 | 
				
			||||||
								</tr>
 | 
					 | 
				
			||||||
							{{end}}
 | 
					 | 
				
			||||||
						{{end}}
 | 
											{{end}}
 | 
				
			||||||
					</tbody>
 | 
										</tbody>
 | 
				
			||||||
				</table>
 | 
									</table>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,13 +7,19 @@ import (
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						git_model "code.gitea.io/gitea/models/git"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unittest"
 | 
						"code.gitea.io/gitea/models/unittest"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/tests"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestRenameBranch(t *testing.T) {
 | 
					func TestRenameBranch(t *testing.T) {
 | 
				
			||||||
 | 
						defer tests.PrepareTestEnv(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: 1, Name: "master"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// get branch setting page
 | 
						// get branch setting page
 | 
				
			||||||
	session := loginUser(t, "user2")
 | 
						session := loginUser(t, "user2")
 | 
				
			||||||
	req := NewRequest(t, "GET", "/user2/repo1/settings/branches")
 | 
						req := NewRequest(t, "GET", "/user2/repo1/settings/branches")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue