code.gitea.io/gitea@v1.21.7/models/git/branch.go (about)

     1  // Copyright 2016 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package git
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"time"
    10  
    11  	"code.gitea.io/gitea/models/db"
    12  	repo_model "code.gitea.io/gitea/models/repo"
    13  	user_model "code.gitea.io/gitea/models/user"
    14  	"code.gitea.io/gitea/modules/git"
    15  	"code.gitea.io/gitea/modules/log"
    16  	"code.gitea.io/gitea/modules/timeutil"
    17  	"code.gitea.io/gitea/modules/util"
    18  
    19  	"xorm.io/builder"
    20  )
    21  
    22  // ErrBranchNotExist represents an error that branch with such name does not exist.
    23  type ErrBranchNotExist struct {
    24  	RepoID     int64
    25  	BranchName string
    26  }
    27  
    28  // IsErrBranchNotExist checks if an error is an ErrBranchDoesNotExist.
    29  func IsErrBranchNotExist(err error) bool {
    30  	_, ok := err.(ErrBranchNotExist)
    31  	return ok
    32  }
    33  
    34  func (err ErrBranchNotExist) Error() string {
    35  	return fmt.Sprintf("branch does not exist [repo_id: %d name: %s]", err.RepoID, err.BranchName)
    36  }
    37  
    38  func (err ErrBranchNotExist) Unwrap() error {
    39  	return util.ErrNotExist
    40  }
    41  
    42  // ErrBranchAlreadyExists represents an error that branch with such name already exists.
    43  type ErrBranchAlreadyExists struct {
    44  	BranchName string
    45  }
    46  
    47  // IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists.
    48  func IsErrBranchAlreadyExists(err error) bool {
    49  	_, ok := err.(ErrBranchAlreadyExists)
    50  	return ok
    51  }
    52  
    53  func (err ErrBranchAlreadyExists) Error() string {
    54  	return fmt.Sprintf("branch already exists [name: %s]", err.BranchName)
    55  }
    56  
    57  func (err ErrBranchAlreadyExists) Unwrap() error {
    58  	return util.ErrAlreadyExist
    59  }
    60  
    61  // ErrBranchNameConflict represents an error that branch name conflicts with other branch.
    62  type ErrBranchNameConflict struct {
    63  	BranchName string
    64  }
    65  
    66  // IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict.
    67  func IsErrBranchNameConflict(err error) bool {
    68  	_, ok := err.(ErrBranchNameConflict)
    69  	return ok
    70  }
    71  
    72  func (err ErrBranchNameConflict) Error() string {
    73  	return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName)
    74  }
    75  
    76  func (err ErrBranchNameConflict) Unwrap() error {
    77  	return util.ErrAlreadyExist
    78  }
    79  
    80  // ErrBranchesEqual represents an error that base branch is equal to the head branch.
    81  type ErrBranchesEqual struct {
    82  	BaseBranchName string
    83  	HeadBranchName string
    84  }
    85  
    86  // IsErrBranchesEqual checks if an error is an ErrBranchesEqual.
    87  func IsErrBranchesEqual(err error) bool {
    88  	_, ok := err.(ErrBranchesEqual)
    89  	return ok
    90  }
    91  
    92  func (err ErrBranchesEqual) Error() string {
    93  	return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName)
    94  }
    95  
    96  func (err ErrBranchesEqual) Unwrap() error {
    97  	return util.ErrInvalidArgument
    98  }
    99  
   100  // Branch represents a branch of a repository
   101  // For those repository who have many branches, stored into database is a good choice
   102  // for pagination, keyword search and filtering
   103  type Branch struct {
   104  	ID            int64
   105  	RepoID        int64  `xorm:"UNIQUE(s)"`
   106  	Name          string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment
   107  	CommitID      string
   108  	CommitMessage string `xorm:"TEXT"` // it only stores the message summary (the first line)
   109  	PusherID      int64
   110  	Pusher        *user_model.User `xorm:"-"`
   111  	IsDeleted     bool             `xorm:"index"`
   112  	DeletedByID   int64
   113  	DeletedBy     *user_model.User   `xorm:"-"`
   114  	DeletedUnix   timeutil.TimeStamp `xorm:"index"`
   115  	CommitTime    timeutil.TimeStamp // The commit
   116  	CreatedUnix   timeutil.TimeStamp `xorm:"created"`
   117  	UpdatedUnix   timeutil.TimeStamp `xorm:"updated"`
   118  }
   119  
   120  func (b *Branch) LoadDeletedBy(ctx context.Context) (err error) {
   121  	if b.DeletedBy == nil {
   122  		b.DeletedBy, err = user_model.GetUserByID(ctx, b.DeletedByID)
   123  		if user_model.IsErrUserNotExist(err) {
   124  			b.DeletedBy = user_model.NewGhostUser()
   125  			err = nil
   126  		}
   127  	}
   128  	return err
   129  }
   130  
   131  func (b *Branch) LoadPusher(ctx context.Context) (err error) {
   132  	if b.Pusher == nil && b.PusherID > 0 {
   133  		b.Pusher, err = user_model.GetUserByID(ctx, b.PusherID)
   134  		if user_model.IsErrUserNotExist(err) {
   135  			b.Pusher = user_model.NewGhostUser()
   136  			err = nil
   137  		}
   138  	}
   139  	return err
   140  }
   141  
   142  func init() {
   143  	db.RegisterModel(new(Branch))
   144  	db.RegisterModel(new(RenamedBranch))
   145  }
   146  
   147  func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, error) {
   148  	var branch Branch
   149  	has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch)
   150  	if err != nil {
   151  		return nil, err
   152  	} else if !has {
   153  		return nil, ErrBranchNotExist{
   154  			RepoID:     repoID,
   155  			BranchName: branchName,
   156  		}
   157  	}
   158  	return &branch, nil
   159  }
   160  
   161  func AddBranches(ctx context.Context, branches []*Branch) error {
   162  	for _, branch := range branches {
   163  		if _, err := db.GetEngine(ctx).Insert(branch); err != nil {
   164  			return err
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  func GetDeletedBranchByID(ctx context.Context, repoID, branchID int64) (*Branch, error) {
   171  	var branch Branch
   172  	has, err := db.GetEngine(ctx).ID(branchID).Get(&branch)
   173  	if err != nil {
   174  		return nil, err
   175  	} else if !has {
   176  		return nil, ErrBranchNotExist{
   177  			RepoID: repoID,
   178  		}
   179  	}
   180  	if branch.RepoID != repoID {
   181  		return nil, ErrBranchNotExist{
   182  			RepoID: repoID,
   183  		}
   184  	}
   185  	if !branch.IsDeleted {
   186  		return nil, ErrBranchNotExist{
   187  			RepoID: repoID,
   188  		}
   189  	}
   190  	return &branch, nil
   191  }
   192  
   193  func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64) error {
   194  	return db.WithTx(ctx, func(ctx context.Context) error {
   195  		branches := make([]*Branch, 0, len(branchIDs))
   196  		if err := db.GetEngine(ctx).In("id", branchIDs).Find(&branches); err != nil {
   197  			return err
   198  		}
   199  		for _, branch := range branches {
   200  			if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil {
   201  				return err
   202  			}
   203  		}
   204  		return nil
   205  	})
   206  }
   207  
   208  // UpdateBranch updates the branch information in the database.
   209  func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) (int64, error) {
   210  	return db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName).
   211  		Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix").
   212  		Update(&Branch{
   213  			CommitID:      commit.ID.String(),
   214  			CommitMessage: commit.Summary(),
   215  			PusherID:      pusherID,
   216  			CommitTime:    timeutil.TimeStamp(commit.Committer.When.Unix()),
   217  			IsDeleted:     false,
   218  		})
   219  }
   220  
   221  // AddDeletedBranch adds a deleted branch to the database
   222  func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error {
   223  	branch, err := GetBranch(ctx, repoID, branchName)
   224  	if err != nil {
   225  		return err
   226  	}
   227  	if branch.IsDeleted {
   228  		return nil
   229  	}
   230  
   231  	cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=? AND is_deleted=?", repoID, branchName, false).
   232  		Cols("is_deleted, deleted_by_id, deleted_unix").
   233  		Update(&Branch{
   234  			IsDeleted:   true,
   235  			DeletedByID: deletedByID,
   236  			DeletedUnix: timeutil.TimeStampNow(),
   237  		})
   238  	if err != nil {
   239  		return err
   240  	}
   241  	if cnt == 0 {
   242  		return fmt.Errorf("branch %s not found or has been deleted", branchName)
   243  	}
   244  	return err
   245  }
   246  
   247  func RemoveDeletedBranchByID(ctx context.Context, repoID, branchID int64) error {
   248  	_, err := db.GetEngine(ctx).Where("repo_id=? AND id=? AND is_deleted = ?", repoID, branchID, true).Delete(new(Branch))
   249  	return err
   250  }
   251  
   252  // RemoveOldDeletedBranches removes old deleted branches
   253  func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) {
   254  	// Nothing to do for shutdown or terminate
   255  	log.Trace("Doing: DeletedBranchesCleanup")
   256  
   257  	deleteBefore := time.Now().Add(-olderThan)
   258  	_, err := db.GetEngine(ctx).Where("is_deleted=? AND deleted_unix < ?", true, deleteBefore.Unix()).Delete(new(Branch))
   259  	if err != nil {
   260  		log.Error("DeletedBranchesCleanup: %v", err)
   261  	}
   262  }
   263  
   264  // RenamedBranch provide renamed branch log
   265  // will check it when a branch can't be found
   266  type RenamedBranch struct {
   267  	ID          int64 `xorm:"pk autoincr"`
   268  	RepoID      int64 `xorm:"INDEX NOT NULL"`
   269  	From        string
   270  	To          string
   271  	CreatedUnix timeutil.TimeStamp `xorm:"created"`
   272  }
   273  
   274  // FindRenamedBranch check if a branch was renamed
   275  func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) {
   276  	branch = &RenamedBranch{
   277  		RepoID: repoID,
   278  		From:   from,
   279  	}
   280  	exist, err = db.GetEngine(ctx).Get(branch)
   281  
   282  	return branch, exist, err
   283  }
   284  
   285  // RenameBranch rename a branch
   286  func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(ctx context.Context, isDefault bool) error) (err error) {
   287  	ctx, committer, err := db.TxContext(ctx)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	defer committer.Close()
   292  
   293  	sess := db.GetEngine(ctx)
   294  
   295  	var branch Branch
   296  	exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch)
   297  	if err != nil {
   298  		return err
   299  	} else if !exist || branch.IsDeleted {
   300  		return ErrBranchNotExist{
   301  			RepoID:     repo.ID,
   302  			BranchName: from,
   303  		}
   304  	}
   305  
   306  	// 1. update branch in database
   307  	if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
   308  		Name: to,
   309  	}); err != nil {
   310  		return err
   311  	} else if n <= 0 {
   312  		return ErrBranchNotExist{
   313  			RepoID:     repo.ID,
   314  			BranchName: from,
   315  		}
   316  	}
   317  
   318  	// 2. update default branch if needed
   319  	isDefault := repo.DefaultBranch == from
   320  	if isDefault {
   321  		repo.DefaultBranch = to
   322  		_, err = sess.ID(repo.ID).Cols("default_branch").Update(repo)
   323  		if err != nil {
   324  			return err
   325  		}
   326  	}
   327  
   328  	// 3. Update protected branch if needed
   329  	protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from)
   330  	if err != nil {
   331  		return err
   332  	}
   333  
   334  	if protectedBranch != nil {
   335  		// there is a protect rule for this branch
   336  		protectedBranch.RuleName = to
   337  		_, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch)
   338  		if err != nil {
   339  			return err
   340  		}
   341  	} else {
   342  		// some glob protect rules may match this branch
   343  		protected, err := IsBranchProtected(ctx, repo.ID, from)
   344  		if err != nil {
   345  			return err
   346  		}
   347  		if protected {
   348  			return ErrBranchIsProtected
   349  		}
   350  	}
   351  
   352  	// 4. Update all not merged pull request base branch name
   353  	_, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?",
   354  		repo.ID, from, false).
   355  		Update(map[string]any{"base_branch": to})
   356  	if err != nil {
   357  		return err
   358  	}
   359  
   360  	// 5. do git action
   361  	if err = gitAction(ctx, isDefault); err != nil {
   362  		return err
   363  	}
   364  
   365  	// 6. insert renamed branch record
   366  	renamedBranch := &RenamedBranch{
   367  		RepoID: repo.ID,
   368  		From:   from,
   369  		To:     to,
   370  	}
   371  	err = db.Insert(ctx, renamedBranch)
   372  	if err != nil {
   373  		return err
   374  	}
   375  
   376  	return committer.Commit()
   377  }
   378  
   379  // FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 6 hours which has no opened PRs created
   380  // except the indicate branch
   381  func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64, excludeBranchName string) (BranchList, error) {
   382  	branches := make(BranchList, 0, 2)
   383  	subQuery := builder.Select("head_branch").From("pull_request").
   384  		InnerJoin("issue", "issue.id = pull_request.issue_id").
   385  		Where(builder.Eq{
   386  			"pull_request.head_repo_id": repoID,
   387  			"issue.is_closed":           false,
   388  		})
   389  	err := db.GetEngine(ctx).
   390  		Where("pusher_id=? AND is_deleted=?", userID, false).
   391  		And("name <> ?", excludeBranchName).
   392  		And("repo_id = ?", repoID).
   393  		And("commit_time >= ?", time.Now().Add(-time.Hour*6).Unix()).
   394  		NotIn("name", subQuery).
   395  		OrderBy("branch.commit_time DESC").
   396  		Limit(2).
   397  		Find(&branches)
   398  	return branches, err
   399  }