Add "Update Branch" button to Pull Requests (#9784)
* add Divergence * add Update Button * first working version * re-use code * split raw merge commands and db-change functions (notify, cache, ...) * use rawMerge (remove redundant code) * own function to get Diverging of PRs * use FlashError * correct Error Msg * hook is triggerd ... so remove comment * add "branch2" to "user2/repo1" because it unit-test "TestPullView_ReviewerMissed" use it but dont exist jet :/ * move GetPerm to IsUserAllowedToUpdate * add Flash Success MSG * imprufe code - remove useless js chage * fix-lint * TEST: add PullRequest ID:5 Repo: user2/repo1 Base: branch1 Head: pr-to-update * correct comments * make PR5 outdated * fix Tests * WIP: add pull update test * update revs * update locales * working TEST * update UI * misspell * change style * add 1s delay so rev exist * move row up (before merge row) * fix lint nit * UI remove divider * Update style * nits * do it right * introduce IsSameRepo * remove useless check Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
parent
9f40bb020e
commit
36943e56d6
@ -134,7 +134,7 @@ func TestAPISearchIssue(t *testing.T) {
|
|||||||
var apiIssues []*api.Issue
|
var apiIssues []*api.Issue
|
||||||
DecodeJSON(t, resp, &apiIssues)
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
|
||||||
assert.Len(t, apiIssues, 8)
|
assert.Len(t, apiIssues, 9)
|
||||||
|
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
query.Add("token", token)
|
query.Add("token", token)
|
||||||
@ -142,7 +142,7 @@ func TestAPISearchIssue(t *testing.T) {
|
|||||||
req = NewRequest(t, "GET", link.String())
|
req = NewRequest(t, "GET", link.String())
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
DecodeJSON(t, resp, &apiIssues)
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
assert.Len(t, apiIssues, 8)
|
assert.Len(t, apiIssues, 9)
|
||||||
|
|
||||||
query.Add("state", "closed")
|
query.Add("state", "closed")
|
||||||
link.RawQuery = query.Encode()
|
link.RawQuery = query.Encode()
|
||||||
@ -163,5 +163,5 @@ func TestAPISearchIssue(t *testing.T) {
|
|||||||
req = NewRequest(t, "GET", link.String())
|
req = NewRequest(t, "GET", link.String())
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
DecodeJSON(t, resp, &apiIssues)
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
assert.Len(t, apiIssues, 0)
|
assert.Len(t, apiIssues, 1)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1,3 @@
|
|||||||
65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master
|
65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master
|
||||||
|
985f0301dba5e7b34be866819cd15ad3d8f508ee refs/heads/branch2
|
||||||
|
62fb502a7172d4453f0322a2cc85bddffa57f07a refs/heads/pr-to-update
|
||||||
|
|||||||
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/5c/050d3b6d2db231ab1f64e324f1b6b9a0b181c2
Normal file
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/5c/050d3b6d2db231ab1f64e324f1b6b9a0b181c2
Normal file
Binary file not shown.
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/62/fb502a7172d4453f0322a2cc85bddffa57f07a
Normal file
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/62/fb502a7172d4453f0322a2cc85bddffa57f07a
Normal file
Binary file not shown.
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/6a/a3a5385611c5eb8986c9961a9c34a93cbaadfb
Normal file
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/6a/a3a5385611c5eb8986c9961a9c34a93cbaadfb
Normal file
Binary file not shown.
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/7c/4df115542e05c700f297519e906fd63c9c9804
Normal file
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/7c/4df115542e05c700f297519e906fd63c9c9804
Normal file
Binary file not shown.
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/94/922e1295c678267de1193b7b84ad8a086c27f9
Normal file
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/94/922e1295c678267de1193b7b84ad8a086c27f9
Normal file
Binary file not shown.
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/98/5f0301dba5e7b34be866819cd15ad3d8f508ee
Normal file
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/98/5f0301dba5e7b34be866819cd15ad3d8f508ee
Normal file
Binary file not shown.
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/a6/9277c81e90b98a7c0ab25b042a6e296da8eb9a
Normal file
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/a6/9277c81e90b98a7c0ab25b042a6e296da8eb9a
Normal file
Binary file not shown.
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/a7/57c0ea621e63d0fd6fc353a175fdc7199e5d1d
Normal file
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/a7/57c0ea621e63d0fd6fc353a175fdc7199e5d1d
Normal file
Binary file not shown.
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/b2/60587271671842af0b036e4fe643c9d45b7ddd
Normal file
BIN
integrations/gitea-repositories-meta/user2/repo1.git/objects/b2/60587271671842af0b036e4fe643c9d45b7ddd
Normal file
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
985f0301dba5e7b34be866819cd15ad3d8f508ee
|
||||||
@ -0,0 +1 @@
|
|||||||
|
62fb502a7172d4453f0322a2cc85bddffa57f07a
|
||||||
@ -0,0 +1 @@
|
|||||||
|
4a357436d925b5c974181ff12a994538ddc5a269
|
||||||
@ -0,0 +1 @@
|
|||||||
|
62fb502a7172d4453f0322a2cc85bddffa57f07a
|
||||||
136
integrations/pull_update_test.go
Normal file
136
integrations/pull_update_test.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package integrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
"code.gitea.io/gitea/modules/repofiles"
|
||||||
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPullUpdate(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||||
|
//Create PR to test
|
||||||
|
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
|
||||||
|
org26 := models.AssertExistsAndLoadBean(t, &models.User{ID: 26}).(*models.User)
|
||||||
|
pr := createOutdatedPR(t, user, org26)
|
||||||
|
|
||||||
|
//Test GetDiverging
|
||||||
|
diffCount, err := pull_service.GetDiverging(pr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, diffCount.Behind)
|
||||||
|
assert.EqualValues(t, 1, diffCount.Ahead)
|
||||||
|
|
||||||
|
message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch)
|
||||||
|
err = pull_service.Update(pr, user, message)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
//Test GetDiverging after update
|
||||||
|
diffCount, err = pull_service.GetDiverging(pr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 0, diffCount.Behind)
|
||||||
|
assert.EqualValues(t, 2, diffCount.Ahead)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOutdatedPR(t *testing.T, actor, forkOrg *models.User) *models.PullRequest {
|
||||||
|
baseRepo, err := repo_service.CreateRepository(actor, actor, models.CreateRepoOptions{
|
||||||
|
Name: "repo-pr-update",
|
||||||
|
Description: "repo-tmp-pr-update description",
|
||||||
|
AutoInit: true,
|
||||||
|
Gitignores: "C,C++",
|
||||||
|
License: "MIT",
|
||||||
|
Readme: "Default",
|
||||||
|
IsPrivate: false,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, baseRepo)
|
||||||
|
|
||||||
|
headRepo, err := repo_module.ForkRepository(actor, forkOrg, baseRepo, "repo-pr-update", "desc")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, headRepo)
|
||||||
|
|
||||||
|
//create a commit on base Repo
|
||||||
|
_, err = repofiles.CreateOrUpdateRepoFile(baseRepo, actor, &repofiles.UpdateRepoFileOptions{
|
||||||
|
TreePath: "File_A",
|
||||||
|
Message: "Add File A",
|
||||||
|
Content: "File A",
|
||||||
|
IsNewFile: true,
|
||||||
|
OldBranch: "master",
|
||||||
|
NewBranch: "master",
|
||||||
|
Author: &repofiles.IdentityOptions{
|
||||||
|
Name: actor.Name,
|
||||||
|
Email: actor.Email,
|
||||||
|
},
|
||||||
|
Committer: &repofiles.IdentityOptions{
|
||||||
|
Name: actor.Name,
|
||||||
|
Email: actor.Email,
|
||||||
|
},
|
||||||
|
Dates: &repofiles.CommitDateOptions{
|
||||||
|
Author: time.Now(),
|
||||||
|
Committer: time.Now(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
//create a commit on head Repo
|
||||||
|
_, err = repofiles.CreateOrUpdateRepoFile(headRepo, actor, &repofiles.UpdateRepoFileOptions{
|
||||||
|
TreePath: "File_B",
|
||||||
|
Message: "Add File on PR branch",
|
||||||
|
Content: "File B",
|
||||||
|
IsNewFile: true,
|
||||||
|
OldBranch: "master",
|
||||||
|
NewBranch: "newBranch",
|
||||||
|
Author: &repofiles.IdentityOptions{
|
||||||
|
Name: actor.Name,
|
||||||
|
Email: actor.Email,
|
||||||
|
},
|
||||||
|
Committer: &repofiles.IdentityOptions{
|
||||||
|
Name: actor.Name,
|
||||||
|
Email: actor.Email,
|
||||||
|
},
|
||||||
|
Dates: &repofiles.CommitDateOptions{
|
||||||
|
Author: time.Now(),
|
||||||
|
Committer: time.Now(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
//create Pull
|
||||||
|
pullIssue := &models.Issue{
|
||||||
|
RepoID: baseRepo.ID,
|
||||||
|
Title: "Test Pull -to-update-",
|
||||||
|
PosterID: actor.ID,
|
||||||
|
Poster: actor,
|
||||||
|
IsPull: true,
|
||||||
|
}
|
||||||
|
pullRequest := &models.PullRequest{
|
||||||
|
HeadRepoID: headRepo.ID,
|
||||||
|
BaseRepoID: baseRepo.ID,
|
||||||
|
HeadBranch: "newBranch",
|
||||||
|
BaseBranch: "master",
|
||||||
|
HeadRepo: headRepo,
|
||||||
|
BaseRepo: baseRepo,
|
||||||
|
Type: models.PullRequestGitea,
|
||||||
|
}
|
||||||
|
err = pull_service.NewPullRequest(baseRepo, pullIssue, nil, nil, pullRequest, nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
issue := models.AssertExistsAndLoadBean(t, &models.Issue{Title: "Test Pull -to-update-"}).(*models.Issue)
|
||||||
|
pr, err := models.GetPullRequestByIssueID(issue.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
return pr
|
||||||
|
}
|
||||||
@ -56,9 +56,9 @@ func TestRepoActivity(t *testing.T) {
|
|||||||
list = htmlDoc.doc.Find("#merged-pull-requests").Next().Find("p.desc")
|
list = htmlDoc.doc.Find("#merged-pull-requests").Next().Find("p.desc")
|
||||||
assert.Len(t, list.Nodes, 1)
|
assert.Len(t, list.Nodes, 1)
|
||||||
|
|
||||||
// Should be 2 merged proposed pull requests
|
// Should be 3 merged proposed pull requests
|
||||||
list = htmlDoc.doc.Find("#proposed-pull-requests").Next().Find("p.desc")
|
list = htmlDoc.doc.Find("#proposed-pull-requests").Next().Find("p.desc")
|
||||||
assert.Len(t, list.Nodes, 2)
|
assert.Len(t, list.Nodes, 3)
|
||||||
|
|
||||||
// Should be 3 new issues
|
// Should be 3 new issues
|
||||||
list = htmlDoc.doc.Find("#new-issues").Next().Find("p.desc")
|
list = htmlDoc.doc.Find("#new-issues").Next().Find("p.desc")
|
||||||
|
|||||||
@ -122,3 +122,15 @@
|
|||||||
created_unix: 946684830
|
created_unix: 946684830
|
||||||
updated_unix: 999307200
|
updated_unix: 999307200
|
||||||
deadline_unix: 1019307200
|
deadline_unix: 1019307200
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 11
|
||||||
|
repo_id: 1
|
||||||
|
index: 5
|
||||||
|
poster_id: 1
|
||||||
|
name: pull5
|
||||||
|
content: content for the a pull request
|
||||||
|
is_closed: false
|
||||||
|
is_pull: true
|
||||||
|
created_unix: 1579194806
|
||||||
|
updated_unix: 1579194806
|
||||||
|
|||||||
@ -50,3 +50,16 @@
|
|||||||
base_branch: master
|
base_branch: master
|
||||||
merge_base: abcdef1234567890
|
merge_base: abcdef1234567890
|
||||||
has_merged: false
|
has_merged: false
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 5 # this PR is outdated (one commit behind branch1 )
|
||||||
|
type: 0 # gitea pull request
|
||||||
|
status: 2 # mergable
|
||||||
|
issue_id: 11
|
||||||
|
index: 5
|
||||||
|
head_repo_id: 1
|
||||||
|
base_repo_id: 1
|
||||||
|
head_branch: pr-to-update
|
||||||
|
base_branch: branch1
|
||||||
|
merge_base: 1234567890abcdef
|
||||||
|
has_merged: false
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
is_private: false
|
is_private: false
|
||||||
num_issues: 2
|
num_issues: 2
|
||||||
num_closed_issues: 1
|
num_closed_issues: 1
|
||||||
num_pulls: 2
|
num_pulls: 3
|
||||||
num_closed_pulls: 0
|
num_closed_pulls: 0
|
||||||
num_milestones: 3
|
num_milestones: 3
|
||||||
num_closed_milestones: 1
|
num_closed_milestones: 1
|
||||||
|
|||||||
@ -276,8 +276,8 @@ func TestIssue_SearchIssueIDsByKeyword(t *testing.T) {
|
|||||||
|
|
||||||
total, ids, err = SearchIssueIDsByKeyword("for", []int64{1}, 10, 0)
|
total, ids, err = SearchIssueIDsByKeyword("for", []int64{1}, 10, 0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 4, total)
|
assert.EqualValues(t, 5, total)
|
||||||
assert.EqualValues(t, []int64{1, 2, 3, 5}, ids)
|
assert.EqualValues(t, []int64{1, 2, 3, 5, 11}, ids)
|
||||||
|
|
||||||
// issue1's comment id 2
|
// issue1's comment id 2
|
||||||
total, ids, err = SearchIssueIDsByKeyword("good", []int64{1}, 10, 0)
|
total, ids, err = SearchIssueIDsByKeyword("good", []int64{1}, 10, 0)
|
||||||
@ -305,8 +305,8 @@ func testInsertIssue(t *testing.T, title, content string) {
|
|||||||
assert.True(t, has)
|
assert.True(t, has)
|
||||||
assert.EqualValues(t, issue.Title, newIssue.Title)
|
assert.EqualValues(t, issue.Title, newIssue.Title)
|
||||||
assert.EqualValues(t, issue.Content, newIssue.Content)
|
assert.EqualValues(t, issue.Content, newIssue.Content)
|
||||||
// there are 4 issues and max index is 4 on repository 1, so this one should 5
|
// there are 5 issues and max index is 5 on repository 1, so this one should 6
|
||||||
assert.EqualValues(t, 5, newIssue.Index)
|
assert.EqualValues(t, 6, newIssue.Index)
|
||||||
|
|
||||||
_, err = x.ID(issue.ID).Delete(new(Issue))
|
_, err = x.ID(issue.ID).Delete(new(Issue))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
@ -17,7 +17,7 @@ func Test_newIssueUsers(t *testing.T) {
|
|||||||
newIssue := &Issue{
|
newIssue := &Issue{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
PosterID: 4,
|
PosterID: 4,
|
||||||
Index: 5,
|
Index: 6,
|
||||||
Title: "newTestIssueTitle",
|
Title: "newTestIssueTitle",
|
||||||
Content: "newTestIssueContent",
|
Content: "newTestIssueContent",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -742,3 +742,8 @@ func (pr *PullRequest) IsHeadEqualWithBranch(branchName string) (bool, error) {
|
|||||||
}
|
}
|
||||||
return baseCommit.HasPreviousCommit(headCommit.ID)
|
return baseCommit.HasPreviousCommit(headCommit.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSameRepo returns true if base repo and head repo is the same
|
||||||
|
func (pr *PullRequest) IsSameRepo() bool {
|
||||||
|
return pr.BaseRepoID == pr.HeadRepoID
|
||||||
|
}
|
||||||
|
|||||||
@ -61,10 +61,11 @@ func TestPullRequestsNewest(t *testing.T) {
|
|||||||
Labels: []string{},
|
Labels: []string{},
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(2), count)
|
assert.EqualValues(t, 3, count)
|
||||||
if assert.Len(t, prs, 2) {
|
if assert.Len(t, prs, 3) {
|
||||||
assert.Equal(t, int64(2), prs[0].ID)
|
assert.EqualValues(t, 5, prs[0].ID)
|
||||||
assert.Equal(t, int64(1), prs[1].ID)
|
assert.EqualValues(t, 2, prs[1].ID)
|
||||||
|
assert.EqualValues(t, 1, prs[2].ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,10 +78,11 @@ func TestPullRequestsOldest(t *testing.T) {
|
|||||||
Labels: []string{},
|
Labels: []string{},
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(2), count)
|
assert.EqualValues(t, 3, count)
|
||||||
if assert.Len(t, prs, 2) {
|
if assert.Len(t, prs, 3) {
|
||||||
assert.Equal(t, int64(1), prs[0].ID)
|
assert.EqualValues(t, 1, prs[0].ID)
|
||||||
assert.Equal(t, int64(2), prs[1].ID)
|
assert.EqualValues(t, 2, prs[1].ID)
|
||||||
|
assert.EqualValues(t, 5, prs[2].ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -65,7 +65,7 @@ func TestBleveSearchIssues(t *testing.T) {
|
|||||||
|
|
||||||
ids, err = SearchIssuesByKeyword([]int64{1}, "for")
|
ids, err = SearchIssuesByKeyword([]int64{1}, "for")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, []int64{1, 2, 3, 5}, ids)
|
assert.EqualValues(t, []int64{1, 2, 3, 5, 11}, ids)
|
||||||
|
|
||||||
ids, err = SearchIssuesByKeyword([]int64{1}, "good")
|
ids, err = SearchIssuesByKeyword([]int64{1}, "good")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -89,7 +89,7 @@ func TestDBSearchIssues(t *testing.T) {
|
|||||||
|
|
||||||
ids, err = SearchIssuesByKeyword([]int64{1}, "for")
|
ids, err = SearchIssuesByKeyword([]int64{1}, "for")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, []int64{1, 2, 3, 5}, ids)
|
assert.EqualValues(t, []int64{1, 2, 3, 5, 11}, ids)
|
||||||
|
|
||||||
ids, err = SearchIssuesByKeyword([]int64{1}, "good")
|
ids, err = SearchIssuesByKeyword([]int64{1}, "good")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
@ -1082,6 +1082,10 @@ pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because
|
|||||||
pulls.status_checking = Some checks are pending
|
pulls.status_checking = Some checks are pending
|
||||||
pulls.status_checks_success = All checks were successful
|
pulls.status_checks_success = All checks were successful
|
||||||
pulls.status_checks_error = Some checks failed
|
pulls.status_checks_error = Some checks failed
|
||||||
|
pulls.update_branch = Update branch
|
||||||
|
pulls.update_branch_success = Branch update was successful
|
||||||
|
pulls.update_not_allowed = You are not allowed to update branch
|
||||||
|
pulls.outdated_with_base_branch = This branch is out-of-date with the base branch
|
||||||
|
|
||||||
milestones.new = New Milestone
|
milestones.new = New Milestone
|
||||||
milestones.open_tab = %d Open
|
milestones.open_tab = %d Open
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/auth"
|
"code.gitea.io/gitea/modules/auth"
|
||||||
@ -342,8 +343,21 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
|
|||||||
|
|
||||||
setMergeTarget(ctx, pull)
|
setMergeTarget(ctx, pull)
|
||||||
|
|
||||||
|
divergence, err := pull_service.GetDiverging(pull)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetDiverging", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx.Data["Divergence"] = divergence
|
||||||
|
allowUpdate, err := pull_service.IsUserAllowedToUpdate(pull, ctx.User)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetDiverging", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx.Data["UpdateAllowed"] = allowUpdate
|
||||||
|
|
||||||
if err := pull.LoadProtectedBranch(); err != nil {
|
if err := pull.LoadProtectedBranch(); err != nil {
|
||||||
ctx.ServerError("GetLatestCommitStatus", err)
|
ctx.ServerError("LoadProtectedBranch", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck
|
ctx.Data["EnableStatusCheck"] = pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck
|
||||||
@ -587,6 +601,72 @@ func ViewPullFiles(ctx *context.Context) {
|
|||||||
ctx.HTML(200, tplPullFiles)
|
ctx.HTML(200, tplPullFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdatePullRequest merge master into PR
|
||||||
|
func UpdatePullRequest(ctx *context.Context) {
|
||||||
|
issue := checkPullInfo(ctx)
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if issue.IsClosed {
|
||||||
|
ctx.NotFound("MergePullRequest", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if issue.PullRequest.HasMerged {
|
||||||
|
ctx.NotFound("MergePullRequest", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := issue.PullRequest.LoadBaseRepo(); err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := issue.PullRequest.LoadHeadRepo(); err != nil {
|
||||||
|
ctx.InternalServerError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
allowedUpdate, err := pull_service.IsUserAllowedToUpdate(issue.PullRequest, ctx.User)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("IsUserAllowedToMerge", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDo: add check if maintainers are allowed to change branch ... (need migration & co)
|
||||||
|
if !allowedUpdate {
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// default merge commit message
|
||||||
|
message := fmt.Sprintf("Merge branch '%s' into %s", issue.PullRequest.BaseBranch, issue.PullRequest.HeadBranch)
|
||||||
|
|
||||||
|
if err = pull_service.Update(issue.PullRequest, ctx.User, message); err != nil {
|
||||||
|
sanitize := func(x string) string {
|
||||||
|
runes := []rune(x)
|
||||||
|
|
||||||
|
if len(runes) > 512 {
|
||||||
|
x = "..." + string(runes[len(runes)-512:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Replace(html.EscapeString(x), "\n", "<br>", -1)
|
||||||
|
}
|
||||||
|
if models.IsErrMergeConflicts(err) {
|
||||||
|
conflictError := err.(models.ErrMergeConflicts)
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.pulls.merge_conflict", sanitize(conflictError.StdErr), sanitize(conflictError.StdOut)))
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Flash.Error(err.Error())
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.pulls.update_branch_success"))
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index))
|
||||||
|
}
|
||||||
|
|
||||||
// MergePullRequest response for merging pull request
|
// MergePullRequest response for merging pull request
|
||||||
func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) {
|
func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) {
|
||||||
issue := checkPullInfo(ctx)
|
issue := checkPullInfo(ctx)
|
||||||
|
|||||||
@ -855,6 +855,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
|||||||
m.Get(".patch", repo.DownloadPullPatch)
|
m.Get(".patch", repo.DownloadPullPatch)
|
||||||
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
|
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
|
||||||
m.Post("/merge", context.RepoMustNotBeArchived(), reqRepoPullsWriter, bindIgnErr(auth.MergePullRequestForm{}), repo.MergePullRequest)
|
m.Post("/merge", context.RepoMustNotBeArchived(), reqRepoPullsWriter, bindIgnErr(auth.MergePullRequestForm{}), repo.MergePullRequest)
|
||||||
|
m.Post("/update", repo.UpdatePullRequest)
|
||||||
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
|
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
|
||||||
m.Group("/files", func() {
|
m.Group("/files", func() {
|
||||||
m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.ViewPullFiles)
|
m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.ViewPullFiles)
|
||||||
|
|||||||
@ -33,11 +33,6 @@ import (
|
|||||||
// Caller should check PR is ready to be merged (review and status checks)
|
// Caller should check PR is ready to be merged (review and status checks)
|
||||||
// FIXME: add repoWorkingPull make sure two merges does not happen at same time.
|
// FIXME: add repoWorkingPull make sure two merges does not happen at same time.
|
||||||
func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repository, mergeStyle models.MergeStyle, message string) (err error) {
|
func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repository, mergeStyle models.MergeStyle, message string) (err error) {
|
||||||
binVersion, err := git.BinVersion()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("git.BinVersion: %v", err)
|
|
||||||
return fmt.Errorf("Unable to get git version: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = pr.GetHeadRepo(); err != nil {
|
if err = pr.GetHeadRepo(); err != nil {
|
||||||
log.Error("GetHeadRepo: %v", err)
|
log.Error("GetHeadRepo: %v", err)
|
||||||
@ -63,6 +58,61 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
|
|||||||
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
|
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if err := rawMerge(pr, doer, mergeStyle, message); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pr.MergedCommitID, err = baseGitRepo.GetBranchCommitID(pr.BaseBranch)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("GetBranchCommit: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pr.MergedUnix = timeutil.TimeStampNow()
|
||||||
|
pr.Merger = doer
|
||||||
|
pr.MergerID = doer.ID
|
||||||
|
|
||||||
|
if err = pr.SetMerged(); err != nil {
|
||||||
|
log.Error("setMerged [%d]: %v", pr.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.NotifyMergePullRequest(pr, doer)
|
||||||
|
|
||||||
|
// Reset cached commit count
|
||||||
|
cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true))
|
||||||
|
|
||||||
|
// Resolve cross references
|
||||||
|
refs, err := pr.ResolveCrossReferences()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("ResolveCrossReferences: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ref := range refs {
|
||||||
|
if err = ref.LoadIssue(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = ref.Issue.LoadRepo(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
close := (ref.RefAction == references.XRefActionCloses)
|
||||||
|
if close != ref.Issue.IsClosed {
|
||||||
|
if err = issue_service.ChangeStatus(ref.Issue, doer, close); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawMerge perform the merge operation without changing any pull information in database
|
||||||
|
func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.MergeStyle, message string) (err error) {
|
||||||
|
binVersion, err := git.BinVersion()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("git.BinVersion: %v", err)
|
||||||
|
return fmt.Errorf("Unable to get git version: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Clone base repo.
|
// Clone base repo.
|
||||||
tmpBasePath, err := createTemporaryRepo(pr)
|
tmpBasePath, err := createTemporaryRepo(pr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -337,46 +387,6 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
|
|||||||
outbuf.Reset()
|
outbuf.Reset()
|
||||||
errbuf.Reset()
|
errbuf.Reset()
|
||||||
|
|
||||||
pr.MergedCommitID, err = baseGitRepo.GetBranchCommitID(pr.BaseBranch)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("GetBranchCommit: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pr.MergedUnix = timeutil.TimeStampNow()
|
|
||||||
pr.Merger = doer
|
|
||||||
pr.MergerID = doer.ID
|
|
||||||
|
|
||||||
if err = pr.SetMerged(); err != nil {
|
|
||||||
log.Error("setMerged [%d]: %v", pr.ID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.NotifyMergePullRequest(pr, doer)
|
|
||||||
|
|
||||||
// Reset cached commit count
|
|
||||||
cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true))
|
|
||||||
|
|
||||||
// Resolve cross references
|
|
||||||
refs, err := pr.ResolveCrossReferences()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("ResolveCrossReferences: %v", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ref := range refs {
|
|
||||||
if err = ref.LoadIssue(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = ref.Issue.LoadRepo(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
close := (ref.RefAction == references.XRefActionCloses)
|
|
||||||
if close != ref.Issue.IsClosed {
|
|
||||||
if err = issue_service.ChangeStatus(ref.Issue, doer, close); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
125
services/pull/update.go
Normal file
125
services/pull/update.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package pull
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update updates pull request with base branch.
|
||||||
|
func Update(pull *models.PullRequest, doer *models.User, message string) error {
|
||||||
|
//use merge functions but switch repo's and branch's
|
||||||
|
pr := &models.PullRequest{
|
||||||
|
HeadRepoID: pull.BaseRepoID,
|
||||||
|
BaseRepoID: pull.HeadRepoID,
|
||||||
|
HeadBranch: pull.BaseBranch,
|
||||||
|
BaseBranch: pull.HeadBranch,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pr.LoadHeadRepo(); err != nil {
|
||||||
|
log.Error("LoadHeadRepo: %v", err)
|
||||||
|
return fmt.Errorf("LoadHeadRepo: %v", err)
|
||||||
|
} else if err = pr.LoadBaseRepo(); err != nil {
|
||||||
|
log.Error("LoadBaseRepo: %v", err)
|
||||||
|
return fmt.Errorf("LoadBaseRepo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
diffCount, err := GetDiverging(pull)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if diffCount.Behind == 0 {
|
||||||
|
return fmt.Errorf("HeadBranch of PR %d is up to date", pull.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
go AddTestPullRequestTask(doer, pr.HeadRepo.ID, pr.HeadBranch, false, "", "")
|
||||||
|
}()
|
||||||
|
|
||||||
|
return rawMerge(pr, doer, models.MergeStyleMerge, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections
|
||||||
|
func IsUserAllowedToUpdate(pull *models.PullRequest, user *models.User) (bool, error) {
|
||||||
|
headRepoPerm, err := models.GetUserRepoPermission(pull.HeadRepo, user)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pr := &models.PullRequest{
|
||||||
|
HeadRepoID: pull.BaseRepoID,
|
||||||
|
BaseRepoID: pull.HeadRepoID,
|
||||||
|
HeadBranch: pull.BaseBranch,
|
||||||
|
BaseBranch: pull.HeadBranch,
|
||||||
|
}
|
||||||
|
return IsUserAllowedToMerge(pr, headRepoPerm, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiverging determines how many commits a PR is ahead or behind the PR base branch
|
||||||
|
func GetDiverging(pr *models.PullRequest) (*git.DivergeObject, error) {
|
||||||
|
log.Trace("PushToBaseRepo[%d]: pushing commits to base repo '%s'", pr.BaseRepoID, pr.GetGitRefName())
|
||||||
|
if err := pr.LoadBaseRepo(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := pr.LoadHeadRepo(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
headRepoPath := pr.HeadRepo.RepoPath()
|
||||||
|
headGitRepo, err := git.OpenRepository(headRepoPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("OpenRepository: %v", err)
|
||||||
|
}
|
||||||
|
defer headGitRepo.Close()
|
||||||
|
|
||||||
|
if pr.IsSameRepo() {
|
||||||
|
diff, err := git.GetDivergingCommits(pr.HeadRepo.RepoPath(), pr.BaseBranch, pr.HeadBranch)
|
||||||
|
return &diff, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpRemoteName := fmt.Sprintf("tmp-pull-%d-base", pr.ID)
|
||||||
|
if err = headGitRepo.AddRemote(tmpRemoteName, pr.BaseRepo.RepoPath(), true); err != nil {
|
||||||
|
return nil, fmt.Errorf("headGitRepo.AddRemote: %v", err)
|
||||||
|
}
|
||||||
|
// Make sure to remove the remote even if the push fails
|
||||||
|
defer func() {
|
||||||
|
if err := headGitRepo.RemoveRemote(tmpRemoteName); err != nil {
|
||||||
|
log.Error("CountDiverging: RemoveRemote: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// $(git rev-list --count tmp-pull-1-base/master..feature) commits ahead of master
|
||||||
|
ahead, errorAhead := checkDivergence(headRepoPath, fmt.Sprintf("%s/%s", tmpRemoteName, pr.BaseBranch), pr.HeadBranch)
|
||||||
|
if errorAhead != nil {
|
||||||
|
return &git.DivergeObject{}, errorAhead
|
||||||
|
}
|
||||||
|
|
||||||
|
// $(git rev-list --count feature..tmp-pull-1-base/master) commits behind master
|
||||||
|
behind, errorBehind := checkDivergence(headRepoPath, pr.HeadBranch, fmt.Sprintf("%s/%s", tmpRemoteName, pr.BaseBranch))
|
||||||
|
if errorBehind != nil {
|
||||||
|
return &git.DivergeObject{}, errorBehind
|
||||||
|
}
|
||||||
|
|
||||||
|
return &git.DivergeObject{Ahead: ahead, Behind: behind}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDivergence(repoPath string, baseBranch string, targetBranch string) (int, error) {
|
||||||
|
branches := fmt.Sprintf("%s..%s", baseBranch, targetBranch)
|
||||||
|
cmd := git.NewCommand("rev-list", "--count", branches)
|
||||||
|
stdout, err := cmd.RunInDir(repoPath)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
outInteger, errInteger := strconv.Atoi(strings.Trim(stdout, "\n"))
|
||||||
|
if errInteger != nil {
|
||||||
|
return -1, errInteger
|
||||||
|
}
|
||||||
|
return outInteger, nil
|
||||||
|
}
|
||||||
@ -157,6 +157,26 @@
|
|||||||
{{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }}
|
{{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if and .Divergence (gt .Divergence.Behind 0)}}
|
||||||
|
<div class="ui very compact branch-update grid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="item text gray eleven wide left floated column">
|
||||||
|
<i class="icon icon-octicon"><span class="octicon octicon-alert"></span></i>
|
||||||
|
{{$.i18n.Tr "repo.pulls.outdated_with_base_branch"}}
|
||||||
|
</div>
|
||||||
|
{{if .UpdateAllowed}}
|
||||||
|
<div class="item text five wide right floated column">
|
||||||
|
<form action="{{.Link}}/update" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
<button class="ui button" data-do="update">
|
||||||
|
<span class="item text">{{$.i18n.Tr "repo.pulls.update_branch"}}</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
{{if .AllowMerge}}
|
{{if .AllowMerge}}
|
||||||
{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}}
|
{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}}
|
||||||
{{$approvers := .Issue.PullRequest.GetApprovers}}
|
{{$approvers := .Issue.PullRequest.GetApprovers}}
|
||||||
|
|||||||
@ -655,6 +655,13 @@
|
|||||||
.icon-octicon {
|
.icon-octicon {
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
}
|
}
|
||||||
|
.branch-update.grid {
|
||||||
|
margin-bottom: -1.5rem;
|
||||||
|
margin-top: -0.5rem;
|
||||||
|
.row {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-item {
|
.review-item {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user