mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 19:38:23 +00:00 
			
		
		
		
	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:
		
							
								
								
									
										379
									
								
								models/git/branch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										379
									
								
								models/git/branch.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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() | ||||
| } | ||||
							
								
								
									
										132
									
								
								models/git/branch_list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								models/git/branch_list.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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" | ||||
| 	repo_model "code.gitea.io/gitea/models/repo" | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
| @@ -18,24 +19,37 @@ import ( | ||||
| func TestAddDeletedBranch(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
| 	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.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "test", "5655464564554545466464656", int64(1))) | ||||
| 	assert.True(t, firstBranch.IsDeleted) | ||||
| 	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) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
| 	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.Len(t, branches, 2) | ||||
| } | ||||
| 
 | ||||
| func TestGetDeletedBranch(t *testing.T) { | ||||
| 	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)) | ||||
| } | ||||
| @@ -43,18 +57,18 @@ func TestGetDeletedBranch(t *testing.T) { | ||||
| func TestDeletedBranchLoadUser(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
| 
 | ||||
| 	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) | ||||
| 	secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2}) | ||||
| 	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) | ||||
| 	secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2}) | ||||
| 
 | ||||
| 	branch := getDeletedBranch(t, firstBranch) | ||||
| 	assert.Nil(t, branch.DeletedBy) | ||||
| 	branch.LoadUser(db.DefaultContext) | ||||
| 	branch.LoadDeletedBy(db.DefaultContext) | ||||
| 	assert.NotNil(t, branch.DeletedBy) | ||||
| 	assert.Equal(t, "user1", branch.DeletedBy.Name) | ||||
| 
 | ||||
| 	branch = getDeletedBranch(t, secondBranch) | ||||
| 	assert.Nil(t, branch.DeletedBy) | ||||
| 	branch.LoadUser(db.DefaultContext) | ||||
| 	branch.LoadDeletedBy(db.DefaultContext) | ||||
| 	assert.NotNil(t, branch.DeletedBy) | ||||
| 	assert.Equal(t, "Ghost", branch.DeletedBy.Name) | ||||
| } | ||||
| @@ -63,22 +77,22 @@ func TestRemoveDeletedBranch(t *testing.T) { | ||||
| 	assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
| 	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) | ||||
| 	assert.NoError(t, err) | ||||
| 	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}) | ||||
| 
 | ||||
| 	deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo.ID, branch.ID) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Equal(t, branch.ID, deletedBranch.ID) | ||||
| 	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) | ||||
| 
 | ||||
| 	return deletedBranch | ||||
| @@ -146,8 +160,8 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) { | ||||
| 
 | ||||
| 	deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo2.ID, 1) | ||||
| 
 | ||||
| 	// Expect no error, and the returned branch is nil. | ||||
| 	assert.NoError(t, err) | ||||
| 	// Expect error, and the returned branch is nil. | ||||
| 	assert.Error(t, err) | ||||
| 	assert.Nil(t, deletedBranch) | ||||
| 
 | ||||
| 	// 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" | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
|  | ||||
| 	"github.com/gobwas/glob" | ||||
| ) | ||||
| @@ -47,19 +47,32 @@ func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedB | ||||
| } | ||||
|  | ||||
| // FindAllMatchedBranches find all matched branches | ||||
| func FindAllMatchedBranches(ctx context.Context, gitRepo *git.Repository, ruleName string) ([]string, error) { | ||||
| 	// FIXME: how many should we get? | ||||
| 	branches, _, err := gitRepo.GetBranchNames(0, 9999999) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	rule := glob.MustCompile(ruleName) | ||||
| 	results := make([]string, 0, len(branches)) | ||||
| 	for _, branch := range branches { | ||||
| 		if rule.Match(branch) { | ||||
| 			results = append(results, branch) | ||||
| func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) { | ||||
| 	results := make([]string, 0, 10) | ||||
| 	for page := 1; ; page++ { | ||||
| 		brancheNames, err := FindBranchNames(ctx, FindBranchOptions{ | ||||
| 			ListOptions: db.ListOptions{ | ||||
| 				PageSize: 100, | ||||
| 				Page:     page, | ||||
| 			}, | ||||
| 			RepoID:          repoID, | ||||
| 			IsDeletedBranch: util.OptionalBoolFalse, | ||||
| 		}) | ||||
| 		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 | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user