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 }