5
5
package pull
6
6
7
7
import (
8
+ "errors"
8
9
"fmt"
10
+ "os"
11
+ "strings"
12
+ "time"
9
13
10
14
"code.gitea.io/gitea/models"
11
15
"code.gitea.io/gitea/modules/git"
12
16
"code.gitea.io/gitea/modules/log"
13
17
)
14
18
15
19
// Update updates pull request with base branch.
16
- func Update (pull * models.PullRequest , doer * models.User , message string ) error {
20
+ func Update (pull * models.PullRequest , doer * models.User , message string , rebase bool ) error {
17
21
//use merge functions but switch repo's and branch's
18
22
pr := & models.PullRequest {
19
23
HeadRepoID : pull .BaseRepoID ,
@@ -37,7 +41,11 @@ func Update(pull *models.PullRequest, doer *models.User, message string) error {
37
41
return fmt .Errorf ("HeadBranch of PR %d is up to date" , pull .Index )
38
42
}
39
43
40
- _ , err = rawMerge (pr , doer , models .MergeStyleMerge , message )
44
+ if rebase {
45
+ err = doRebase (pr , doer )
46
+ } else {
47
+ _ , err = rawMerge (pr , doer , models .MergeStyleMerge , message )
48
+ }
41
49
42
50
defer func () {
43
51
go AddTestPullRequestTask (doer , pr .HeadRepo .ID , pr .HeadBranch , false , "" , "" )
@@ -47,7 +55,7 @@ func Update(pull *models.PullRequest, doer *models.User, message string) error {
47
55
}
48
56
49
57
// IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections
50
- func IsUserAllowedToUpdate (pull * models.PullRequest , user * models.User ) (bool , error ) {
58
+ func IsUserAllowedToUpdate (pull * models.PullRequest , user * models.User , rebase bool ) (bool , error ) {
51
59
if user == nil {
52
60
return false , nil
53
61
}
@@ -68,6 +76,11 @@ func IsUserAllowedToUpdate(pull *models.PullRequest, user *models.User) (bool, e
68
76
return false , err
69
77
}
70
78
79
+ // can't do rebase on protected branch because need force push
80
+ if rebase && pr .ProtectedBranch != nil {
81
+ return false , err
82
+ }
83
+
71
84
// Update function need push permission
72
85
if pr .ProtectedBranch != nil && ! pr .ProtectedBranch .CanUserPush (user .ID ) {
73
86
return false , nil
@@ -100,3 +113,83 @@ func GetDiverging(pr *models.PullRequest) (*git.DivergeObject, error) {
100
113
diff , err := git .GetDivergingCommits (tmpRepo , "base" , "tracking" )
101
114
return & diff , err
102
115
}
116
+
117
+ func doRebase (pr * models.PullRequest , doer * models.User ) error {
118
+ // 1. Clone base repo.
119
+ tmpBasePath , err := createTemporaryRepo (pr )
120
+ if err != nil {
121
+ log .Error ("CreateTemporaryPath: %v" , err )
122
+ return err
123
+ }
124
+ defer func () {
125
+ if err := models .RemoveTemporaryPath (tmpBasePath ); err != nil {
126
+ log .Error ("Update-By-Rebase: RemoveTemporaryPath: %s" , err )
127
+ }
128
+ }()
129
+
130
+ baseBranch := "base"
131
+ trackingBranch := "tracking"
132
+
133
+ // 2. checkout base branch
134
+ msg , err := git .NewCommand ("checkout" , baseBranch ).RunInDir (tmpBasePath )
135
+ if err != nil {
136
+ return errors .New (msg )
137
+ }
138
+
139
+ // 3. do rebase to ttacking branch
140
+ sig := doer .NewGitSig ()
141
+ committer := sig
142
+
143
+ // Determine if we should sign
144
+ signArg := ""
145
+ if git .CheckGitVersionAtLeast ("1.7.9" ) == nil {
146
+ sign , keyID , signer , _ := pr .SignMerge (doer , tmpBasePath , "HEAD" , trackingBranch )
147
+ if sign {
148
+ signArg = "-S" + keyID
149
+ if pr .BaseRepo .GetTrustModel () == models .CommitterTrustModel || pr .BaseRepo .GetTrustModel () == models .CollaboratorCommitterTrustModel {
150
+ committer = signer
151
+ }
152
+ } else if git .CheckGitVersionAtLeast ("2.0.0" ) == nil {
153
+ signArg = "--no-gpg-sign"
154
+ }
155
+ }
156
+
157
+ commitTimeStr := time .Now ().Format (time .RFC3339 )
158
+
159
+ // Because this may call hooks we should pass in the environment
160
+ env := append (os .Environ (),
161
+ "GIT_AUTHOR_NAME=" + sig .Name ,
162
+ "GIT_AUTHOR_EMAIL=" + sig .Email ,
163
+ "GIT_AUTHOR_DATE=" + commitTimeStr ,
164
+ "GIT_COMMITTER_NAME=" + committer .Name ,
165
+ "GIT_COMMITTER_EMAIL=" + committer .Email ,
166
+ "GIT_COMMITTER_DATE=" + commitTimeStr ,
167
+ )
168
+
169
+ var outbuf , errbuf strings.Builder
170
+ err = git .NewCommand ("rebase" , trackingBranch , signArg ).RunInDirTimeoutEnvFullPipeline (env , - 1 , tmpBasePath , & outbuf , & errbuf , nil )
171
+ if err != nil {
172
+ log .Error ("git rebase [%s:%s -> %s:%s]: %v\n %s\n %s" , pr .BaseRepo .FullName (), pr .BaseBranch , pr .HeadRepo .FullName (), pr .HeadBranch , err , outbuf .String (), errbuf .String ())
173
+ return fmt .Errorf ("git rebase [%s:%s -> %s:%s]: %v\n %s\n %s" , pr .BaseRepo .FullName (), pr .BaseBranch , pr .HeadRepo .FullName (), pr .HeadBranch , err , outbuf .String (), errbuf .String ())
174
+ }
175
+
176
+ // 4. force push to base branch
177
+ env = models .FullPushingEnvironment (doer , doer , pr .BaseRepo , pr .BaseRepo .Name , pr .ID )
178
+
179
+ outbuf .Reset ()
180
+ errbuf .Reset ()
181
+ if err := git .NewCommand ("push" , "-f" , "origin" , baseBranch + ":refs/heads/" + pr .BaseBranch ).RunInDirTimeoutEnvPipeline (env , - 1 , tmpBasePath , & outbuf , & errbuf ); err != nil {
182
+ if strings .Contains (errbuf .String (), "! [remote rejected]" ) {
183
+ err := & git.ErrPushRejected {
184
+ StdOut : outbuf .String (),
185
+ StdErr : errbuf .String (),
186
+ Err : err ,
187
+ }
188
+ err .GenerateMessage ()
189
+ return err
190
+ }
191
+ return fmt .Errorf ("git force push: %s" , errbuf .String ())
192
+ }
193
+
194
+ return nil
195
+ }
0 commit comments