1// Copyright 2020 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package pull
5
6import (
7 "context"
8 "fmt"
9
10 git_model "forgejo.org/models/git"
11 issues_model "forgejo.org/models/issues"
12 access_model "forgejo.org/models/perm/access"
13 repo_model "forgejo.org/models/repo"
14 "forgejo.org/models/unit"
15 user_model "forgejo.org/models/user"
16 "forgejo.org/modules/git"
17 "forgejo.org/modules/log"
18 "forgejo.org/modules/repository"
19)
20
21// Update updates pull request with base branch.
22func 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 AddTestPullRequestTask(ctx, doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "", 0)
41 }()
42
43 return updateHeadByRebaseOnToBase(ctx, pr, doer)
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 AddTestPullRequestTask(ctx, doer, reversePR.HeadRepo.ID, reversePR.HeadBranch, false, "", "", 0)
80 }()
81
82 return err
83}
84
85// IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections
86func 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
167func 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}