Skip to content

Commit be765ca

Browse files
committed
add option to update pull request by rebase
Signed-off-by: a1012112796 <1012112796@qq.com>
1 parent 3dafb07 commit be765ca

File tree

6 files changed

+186
-10
lines changed

6 files changed

+186
-10
lines changed

integrations/pull_update_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,34 @@ func TestAPIPullUpdate(t *testing.T) {
4747
})
4848
}
4949

50+
func TestAPIPullUpdateByRebase(t *testing.T) {
51+
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
52+
//Create PR to test
53+
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
54+
org26 := models.AssertExistsAndLoadBean(t, &models.User{ID: 26}).(*models.User)
55+
pr := createOutdatedPR(t, user, org26)
56+
57+
//Test GetDiverging
58+
diffCount, err := pull_service.GetDiverging(pr)
59+
assert.NoError(t, err)
60+
assert.EqualValues(t, 1, diffCount.Behind)
61+
assert.EqualValues(t, 1, diffCount.Ahead)
62+
assert.NoError(t, pr.LoadBaseRepo())
63+
assert.NoError(t, pr.LoadIssue())
64+
65+
session := loginUser(t, "user2")
66+
token := getTokenForLoggedInUser(t, session)
67+
req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase&token="+token, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index)
68+
session.MakeRequest(t, req, http.StatusOK)
69+
70+
//Test GetDiverging after update
71+
diffCount, err = pull_service.GetDiverging(pr)
72+
assert.NoError(t, err)
73+
assert.EqualValues(t, 0, diffCount.Behind)
74+
assert.EqualValues(t, 1, diffCount.Ahead)
75+
})
76+
}
77+
5078
func createOutdatedPR(t *testing.T, actor, forkOrg *models.User) *models.PullRequest {
5179
baseRepo, err := repo_service.CreateRepository(actor, actor, models.CreateRepoOptions{
5280
Name: "repo-pr-update",

routers/api/v1/repo/pull.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,11 @@ func UpdatePullRequest(ctx *context.APIContext) {
10301030
// type: integer
10311031
// format: int64
10321032
// required: true
1033+
// - name: style
1034+
// in: query
1035+
// description: how to update pull request
1036+
// type: string
1037+
// enum: [merge, rebase]
10331038
// responses:
10341039
// "200":
10351040
// "$ref": "#/responses/empty"
@@ -1076,7 +1081,9 @@ func UpdatePullRequest(ctx *context.APIContext) {
10761081
return
10771082
}
10781083

1079-
allowedUpdate, err := pull_service.IsUserAllowedToUpdate(pr, ctx.User)
1084+
rebase := ctx.Query("style") == "rebase"
1085+
1086+
allowedUpdate, err := pull_service.IsUserAllowedToUpdate(pr, ctx.User, rebase)
10801087
if err != nil {
10811088
ctx.Error(http.StatusInternalServerError, "IsUserAllowedToMerge", err)
10821089
return
@@ -1090,7 +1097,7 @@ func UpdatePullRequest(ctx *context.APIContext) {
10901097
// default merge commit message
10911098
message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch)
10921099

1093-
if err = pull_service.Update(pr, ctx.User, message); err != nil {
1100+
if err = pull_service.Update(pr, ctx.User, message, rebase); err != nil {
10941101
if models.IsErrMergeConflicts(err) {
10951102
ctx.Error(http.StatusConflict, "Update", "merge failed because of conflict")
10961103
return

routers/web/repo/pull.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,13 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
439439
}
440440

441441
if headBranchExist {
442-
ctx.Data["UpdateAllowed"], err = pull_service.IsUserAllowedToUpdate(pull, ctx.User)
442+
b, err := models.GetProtectedBranchBy(pull.HeadRepoID, pull.HeadBranch)
443+
if err != nil {
444+
ctx.ServerError("GetProtectedBranchBy", err)
445+
return nil
446+
}
447+
ctx.Data["UpdateByRebaseNotAllowed"] = b != nil
448+
ctx.Data["UpdateAllowed"], err = pull_service.IsUserAllowedToUpdate(pull, ctx.User, false)
443449
if err != nil {
444450
ctx.ServerError("IsUserAllowedToUpdate", err)
445451
return nil
@@ -712,6 +718,8 @@ func UpdatePullRequest(ctx *context.Context) {
712718
return
713719
}
714720

721+
rebase := ctx.Query("style") == "rebase"
722+
715723
if err := issue.PullRequest.LoadBaseRepo(); err != nil {
716724
ctx.ServerError("LoadBaseRepo", err)
717725
return
@@ -721,7 +729,7 @@ func UpdatePullRequest(ctx *context.Context) {
721729
return
722730
}
723731

724-
allowedUpdate, err := pull_service.IsUserAllowedToUpdate(issue.PullRequest, ctx.User)
732+
allowedUpdate, err := pull_service.IsUserAllowedToUpdate(issue.PullRequest, ctx.User, rebase)
725733
if err != nil {
726734
ctx.ServerError("IsUserAllowedToMerge", err)
727735
return
@@ -737,7 +745,7 @@ func UpdatePullRequest(ctx *context.Context) {
737745
// default merge commit message
738746
message := fmt.Sprintf("Merge branch '%s' into %s", issue.PullRequest.BaseBranch, issue.PullRequest.HeadBranch)
739747

740-
if err = pull_service.Update(issue.PullRequest, ctx.User, message); err != nil {
748+
if err = pull_service.Update(issue.PullRequest, ctx.User, message, rebase); err != nil {
741749
if models.IsErrMergeConflicts(err) {
742750
conflictError := err.(models.ErrMergeConflicts)
743751
flashError, err := ctx.HTMLString(string(tplAlertDetails), map[string]interface{}{

services/pull/update.go

+96-3
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
package pull
66

77
import (
8+
"errors"
89
"fmt"
10+
"os"
11+
"strings"
12+
"time"
913

1014
"code.gitea.io/gitea/models"
1115
"code.gitea.io/gitea/modules/git"
1216
"code.gitea.io/gitea/modules/log"
1317
)
1418

1519
// 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 {
1721
//use merge functions but switch repo's and branch's
1822
pr := &models.PullRequest{
1923
HeadRepoID: pull.BaseRepoID,
@@ -37,7 +41,11 @@ func Update(pull *models.PullRequest, doer *models.User, message string) error {
3741
return fmt.Errorf("HeadBranch of PR %d is up to date", pull.Index)
3842
}
3943

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+
}
4149

4250
defer func() {
4351
go AddTestPullRequestTask(doer, pr.HeadRepo.ID, pr.HeadBranch, false, "", "")
@@ -47,7 +55,7 @@ func Update(pull *models.PullRequest, doer *models.User, message string) error {
4755
}
4856

4957
// 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) {
5159
if user == nil {
5260
return false, nil
5361
}
@@ -68,6 +76,11 @@ func IsUserAllowedToUpdate(pull *models.PullRequest, user *models.User) (bool, e
6876
return false, err
6977
}
7078

79+
// can't do rebase on protected branch because need force push
80+
if rebase && pr.ProtectedBranch != nil {
81+
return false, err
82+
}
83+
7184
// Update function need push permission
7285
if pr.ProtectedBranch != nil && !pr.ProtectedBranch.CanUserPush(user.ID) {
7386
return false, nil
@@ -100,3 +113,83 @@ func GetDiverging(pr *models.PullRequest) (*git.DivergeObject, error) {
100113
diff, err := git.GetDivergingCommits(tmpRepo, "base", "tracking")
101114
return &diff, err
102115
}
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+
}

templates/repo/issue/view_content/pull.tmpl

+32-2
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,22 @@
281281
{{$.i18n.Tr "repo.pulls.outdated_with_base_branch"}}
282282
</div>
283283
<div class="item-section-right">
284-
{{if .UpdateAllowed}}
284+
{{if and .UpdateAllowed (not .UpdateByRebaseNotAllowed)}}
285+
<div class="ui dropdown jump item" tabindex="-1" data-variation="tiny inverted">
286+
<span class="text">
287+
{{$.i18n.Tr "repo.pulls.update_branch"}}
288+
</span>
289+
<div class="menu user-menu" tabindex="-1">
290+
<a class="item link-action" href data-url="{{.Link}}/update" data-redirect="{{.Link}}">
291+
merge
292+
</a>
293+
<a class="item ui red link-action" href data-url="{{.Link}}/update?style=rebase" data-redirect="{{.Link}}">
294+
rebase
295+
</a>
296+
</div>
297+
</div>
298+
{{end}}
299+
{{if and .UpdateAllowed .UpdateByRebaseNotAllowed}}
285300
<form action="{{.Link}}/update" method="post" class="ui update-branch-form">
286301
{{.CsrfTokenHtml}}
287302
<button class="ui compact button" data-do="update">
@@ -526,7 +541,22 @@
526541
{{$.i18n.Tr "repo.pulls.outdated_with_base_branch"}}
527542
</div>
528543
<div>
529-
{{if .UpdateAllowed}}
544+
{{if and .UpdateAllowed (not .UpdateByRebaseNotAllowed)}}
545+
<div class="ui dropdown jump item" tabindex="-1" data-variation="tiny inverted">
546+
<span class="text">
547+
{{$.i18n.Tr "repo.pulls.update_branch"}}
548+
</span>
549+
<div class="menu user-menu" tabindex="-1">
550+
<a class="item link-action" href data-url="{{.Link}}/update" data-redirect="{{.Link}}">
551+
merge
552+
</a>
553+
<a class="item ui red link-action" href data-url="{{.Link}}/update?style=rebase" data-redirect="{{.Link}}">
554+
rebase
555+
</a>
556+
</div>
557+
</div>
558+
{{end}}
559+
{{if and .UpdateAllowed .UpdateByRebaseNotAllowed}}
530560
<form action="{{.Link}}/update" method="post">
531561
{{.CsrfTokenHtml}}
532562
<button class="ui compact button" data-do="update">

templates/swagger/v1_json.tmpl

+10
Original file line numberDiff line numberDiff line change
@@ -7911,6 +7911,16 @@
79117911
"name": "index",
79127912
"in": "path",
79137913
"required": true
7914+
},
7915+
{
7916+
"enum": [
7917+
"merge",
7918+
"rebase"
7919+
],
7920+
"type": "string",
7921+
"description": "how to update pull request",
7922+
"name": "style",
7923+
"in": "query"
79147924
}
79157925
],
79167926
"responses": {

0 commit comments

Comments
 (0)