* 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>for-closed-social
@ -1 +1,3 @@ | |||
65f1bf27bc3bf70f64657658635e66094edbcb4d refs/heads/master | |||
985f0301dba5e7b34be866819cd15ad3d8f508ee refs/heads/branch2 | |||
62fb502a7172d4453f0322a2cc85bddffa57f07a refs/heads/pr-to-update |
@ -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 | |||
} |