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