code.gitea.io/gitea@v1.21.7/services/pull/update.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package pull
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	git_model "code.gitea.io/gitea/models/git"
    11  	issues_model "code.gitea.io/gitea/models/issues"
    12  	access_model "code.gitea.io/gitea/models/perm/access"
    13  	repo_model "code.gitea.io/gitea/models/repo"
    14  	"code.gitea.io/gitea/models/unit"
    15  	user_model "code.gitea.io/gitea/models/user"
    16  	"code.gitea.io/gitea/modules/git"
    17  	"code.gitea.io/gitea/modules/log"
    18  )
    19  
    20  // Update updates pull request with base branch.
    21  func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, message string, rebase bool) error {
    22  	if pr.Flow == issues_model.PullRequestFlowAGit {
    23  		// TODO: update of agit flow pull request's head branch is unsupported
    24  		return fmt.Errorf("update of agit flow pull request's head branch is unsupported")
    25  	}
    26  
    27  	pullWorkingPool.CheckIn(fmt.Sprint(pr.ID))
    28  	defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID))
    29  
    30  	diffCount, err := GetDiverging(ctx, pr)
    31  	if err != nil {
    32  		return err
    33  	} else if diffCount.Behind == 0 {
    34  		return fmt.Errorf("HeadBranch of PR %d is up to date", pr.Index)
    35  	}
    36  
    37  	if rebase {
    38  		defer func() {
    39  			go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
    40  		}()
    41  
    42  		return updateHeadByRebaseOnToBase(ctx, pr, doer, message)
    43  	}
    44  
    45  	if err := pr.LoadBaseRepo(ctx); err != nil {
    46  		log.Error("unable to load BaseRepo for %-v during update-by-merge: %v", pr, err)
    47  		return fmt.Errorf("unable to load BaseRepo for PR[%d] during update-by-merge: %w", pr.ID, err)
    48  	}
    49  	if err := pr.LoadHeadRepo(ctx); err != nil {
    50  		log.Error("unable to load HeadRepo for PR %-v during update-by-merge: %v", pr, err)
    51  		return fmt.Errorf("unable to load HeadRepo for PR[%d] during update-by-merge: %w", pr.ID, err)
    52  	}
    53  	if pr.HeadRepo == nil {
    54  		// LoadHeadRepo will swallow ErrRepoNotExist so if pr.HeadRepo is still nil recreate the error
    55  		err := repo_model.ErrRepoNotExist{
    56  			ID: pr.HeadRepoID,
    57  		}
    58  		log.Error("unable to load HeadRepo for PR %-v during update-by-merge: %v", pr, err)
    59  		return fmt.Errorf("unable to load HeadRepo for PR[%d] during update-by-merge: %w", pr.ID, err)
    60  	}
    61  
    62  	// use merge functions but switch repos and branches
    63  	reversePR := &issues_model.PullRequest{
    64  		ID: pr.ID,
    65  
    66  		HeadRepoID: pr.BaseRepoID,
    67  		HeadRepo:   pr.BaseRepo,
    68  		HeadBranch: pr.BaseBranch,
    69  
    70  		BaseRepoID: pr.HeadRepoID,
    71  		BaseRepo:   pr.HeadRepo,
    72  		BaseBranch: pr.HeadBranch,
    73  	}
    74  
    75  	_, err = doMergeAndPush(ctx, reversePR, doer, repo_model.MergeStyleMerge, "", message)
    76  
    77  	defer func() {
    78  		go AddTestPullRequestTask(doer, reversePR.HeadRepo.ID, reversePR.HeadBranch, false, "", "")
    79  	}()
    80  
    81  	return err
    82  }
    83  
    84  // IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections
    85  func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, user *user_model.User) (mergeAllowed, rebaseAllowed bool, err error) {
    86  	if pull.Flow == issues_model.PullRequestFlowAGit {
    87  		return false, false, nil
    88  	}
    89  
    90  	if user == nil {
    91  		return false, false, nil
    92  	}
    93  	headRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, user)
    94  	if err != nil {
    95  		if repo_model.IsErrUnitTypeNotExist(err) {
    96  			return false, false, nil
    97  		}
    98  		return false, false, err
    99  	}
   100  
   101  	if err := pull.LoadBaseRepo(ctx); err != nil {
   102  		return false, false, err
   103  	}
   104  
   105  	pr := &issues_model.PullRequest{
   106  		HeadRepoID: pull.BaseRepoID,
   107  		HeadRepo:   pull.BaseRepo,
   108  		BaseRepoID: pull.HeadRepoID,
   109  		BaseRepo:   pull.HeadRepo,
   110  		HeadBranch: pull.BaseBranch,
   111  		BaseBranch: pull.HeadBranch,
   112  	}
   113  
   114  	pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch)
   115  	if err != nil {
   116  		return false, false, err
   117  	}
   118  
   119  	// can't do rebase on protected branch because need force push
   120  	if pb == nil {
   121  		if err := pr.LoadBaseRepo(ctx); err != nil {
   122  			return false, false, err
   123  		}
   124  		prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
   125  		if err != nil {
   126  			if repo_model.IsErrUnitTypeNotExist(err) {
   127  				return false, false, nil
   128  			}
   129  			log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err)
   130  			return false, false, err
   131  		}
   132  		rebaseAllowed = prUnit.PullRequestsConfig().AllowRebaseUpdate
   133  	}
   134  
   135  	// Update function need push permission
   136  	if pb != nil {
   137  		pb.Repo = pull.BaseRepo
   138  		if !pb.CanUserPush(ctx, user) {
   139  			return false, false, nil
   140  		}
   141  	}
   142  
   143  	baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, user)
   144  	if err != nil {
   145  		return false, false, err
   146  	}
   147  
   148  	mergeAllowed, err = IsUserAllowedToMerge(ctx, pr, headRepoPerm, user)
   149  	if err != nil {
   150  		return false, false, err
   151  	}
   152  
   153  	if pull.AllowMaintainerEdit {
   154  		mergeAllowedMaintainer, err := IsUserAllowedToMerge(ctx, pr, baseRepoPerm, user)
   155  		if err != nil {
   156  			return false, false, err
   157  		}
   158  
   159  		mergeAllowed = mergeAllowed || mergeAllowedMaintainer
   160  	}
   161  
   162  	return mergeAllowed, rebaseAllowed, nil
   163  }
   164  
   165  // GetDiverging determines how many commits a PR is ahead or behind the PR base branch
   166  func GetDiverging(ctx context.Context, pr *issues_model.PullRequest) (*git.DivergeObject, error) {
   167  	log.Trace("GetDiverging[%-v]: compare commits", pr)
   168  	prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
   169  	if err != nil {
   170  		if !git_model.IsErrBranchNotExist(err) {
   171  			log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
   172  		}
   173  		return nil, err
   174  	}
   175  	defer cancel()
   176  
   177  	diff, err := git.GetDivergingCommits(ctx, prCtx.tmpBasePath, baseBranch, trackingBranch)
   178  	return &diff, err
   179  }