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>tokarchuk/v1.17
parent
9f40bb020e
commit
36943e56d6
@ -1 +1,3 @@ |
|||||||
65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master |
65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master |
||||||
|
985f0301dba5e7b34be866819cd15ad3d8f508ee refs/heads/branch2 |
||||||
|
62fb502a7172d4453f0322a2cc85bddffa57f07a refs/heads/pr-to-update |
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@ |
|||||||
|
985f0301dba5e7b34be866819cd15ad3d8f508ee |
@ -0,0 +1 @@ |
|||||||
|
62fb502a7172d4453f0322a2cc85bddffa57f07a |
@ -0,0 +1 @@ |
|||||||
|
4a357436d925b5c974181ff12a994538ddc5a269 |
@ -0,0 +1 @@ |
|||||||
|
62fb502a7172d4453f0322a2cc85bddffa57f07a |
@ -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 |
||||||
|
} |
@ -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 |
||||||
|
} |
Loading…
Reference in new issue