summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
author6543 <6543@obermui.de>2020-01-17 07:03:40 +0100
committerLauris BH <lauris@nix.lv>2020-01-17 08:03:40 +0200
commit36943e56d66a2d711a6b0c27219ce91a3ddc020a (patch)
tree6e1c57454beeaa9d64470c82db1140a5f540ad65 /services
parent9f40bb020eaea153eca77d3071a4f2cc8bcd2a8e (diff)
downloadgitea-36943e56d66a2d711a6b0c27219ce91a3ddc020a.tar.gz
gitea-36943e56d66a2d711a6b0c27219ce91a3ddc020a.zip
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>
Diffstat (limited to 'services')
-rw-r--r--services/pull/merge.go100
-rw-r--r--services/pull/update.go125
2 files changed, 180 insertions, 45 deletions
diff --git a/services/pull/merge.go b/services/pull/merge.go
index b423c663ea..26c9ab3d1c 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -33,11 +33,6 @@ import (
// 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.
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 {
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, "", "")
}()
+ 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.
tmpBasePath, err := createTemporaryRepo(pr)
if err != nil {
@@ -337,46 +387,6 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
outbuf.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
}
diff --git a/services/pull/update.go b/services/pull/update.go
new file mode 100644
index 0000000000..5f055827e1
--- /dev/null
+++ b/services/pull/update.go
@@ -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
+}