diff options
author | Lauris BH <lauris@nix.lv> | 2018-01-05 20:56:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-01-05 20:56:50 +0200 |
commit | 8ac1501ad7f8b350bd9c1bf01b25f994fd3560f9 (patch) | |
tree | cf9b661996134da3e39aa6ddd2af4e13f7dbf30f /models/pull.go | |
parent | a192f3052ed9b59d1404fdcebf2b5c156d6d6969 (diff) | |
download | gitea-8ac1501ad7f8b350bd9c1bf01b25f994fd3560f9.tar.gz gitea-8ac1501ad7f8b350bd9c1bf01b25f994fd3560f9.zip |
Add Pull Request merge options - Ignore white-space for conflict checking, Rebase, Squash merge (#3188)
* Pull request options migration and UI in settings
* Add ignore whitespace functionality
* Fix settings if pull requests are disabled
* Fix migration transaction
* Merge with Rebase functionality
* UI changes and related functionality for pull request merging button
* Implement squash functionality
* Fix rebase merging
* Fix pull request merge tests
* Add squash and rebase tests
* Fix API method to reuse default message functions
* Some refactoring and small fixes
* Remove more hardcoded values from tests
* Remove unneeded check from API method
* Fix variable name and comment typo
* Fix reset commit count after PR merge
Diffstat (limited to 'models/pull.go')
-rw-r--r-- | models/pull.go | 138 |
1 files changed, 122 insertions, 16 deletions
diff --git a/models/pull.go b/models/pull.go index 47fc1dfb61..c9357e9130 100644 --- a/models/pull.go +++ b/models/pull.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/git" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" @@ -109,6 +110,28 @@ func (pr *PullRequest) loadIssue(e Engine) (err error) { return err } +// GetDefaultMergeMessage returns default message used when merging pull request +func (pr *PullRequest) GetDefaultMergeMessage() string { + if pr.HeadRepo == nil { + var err error + pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID) + if err != nil { + log.Error(4, "GetRepositoryById[%d]: %v", pr.HeadRepoID, err) + return "" + } + } + return fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch) +} + +// GetDefaultSquashMessage returns default message used when squash and merging pull request +func (pr *PullRequest) GetDefaultSquashMessage() string { + if err := pr.LoadIssue(); err != nil { + log.Error(4, "LoadIssue: %v", err) + return "" + } + return fmt.Sprintf("%s (#%d)", pr.Issue.Title, pr.Issue.Index) +} + // APIFormat assumes following fields have been assigned with valid values: // Required - Issue // Optional - Merger @@ -232,15 +255,38 @@ func (pr *PullRequest) CanAutoMerge() bool { return pr.Status == PullRequestStatusMergeable } +// MergeStyle represents the approach to merge commits into base branch. +type MergeStyle string + +const ( + // MergeStyleMerge create merge commit + MergeStyleMerge MergeStyle = "merge" + // MergeStyleRebase rebase before merging + MergeStyleRebase MergeStyle = "rebase" + // MergeStyleSquash squash commits into single commit before merging + MergeStyleSquash MergeStyle = "squash" +) + // Merge merges pull request to base repository. // FIXME: add repoWorkingPull make sure two merges does not happen at same time. -func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) { +func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle MergeStyle, message string) (err error) { if err = pr.GetHeadRepo(); err != nil { return fmt.Errorf("GetHeadRepo: %v", err) } else if err = pr.GetBaseRepo(); err != nil { return fmt.Errorf("GetBaseRepo: %v", err) } + prUnit, err := pr.BaseRepo.GetUnit(UnitTypePullRequests) + if err != nil { + return err + } + prConfig := prUnit.PullRequestsConfig() + + // Check if merge style is correct and allowed + if !prConfig.IsMergeStyleAllowed(mergeStyle) { + return ErrInvalidMergeStyle{pr.BaseRepo.ID, mergeStyle} + } + defer func() { go HookQueue.Add(pr.BaseRepo.ID) go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false) @@ -289,18 +335,62 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) } - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, - fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath), - "git", "merge", "--no-ff", "--no-commit", "head_repo/"+pr.HeadBranch); err != nil { - return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr) - } + switch mergeStyle { + case MergeStyleMerge: + if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath), + "git", "merge", "--no-ff", "--no-commit", "head_repo/"+pr.HeadBranch); err != nil { + return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr) + } - sig := doer.NewGitSig() - if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, - fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), - "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), - "-m", fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch)); err != nil { - return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr) + sig := doer.NewGitSig() + if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath), + "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), + "-m", message); err != nil { + return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr) + } + case MergeStyleRebase: + // Checkout head branch + if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), + "git", "checkout", "-b", "head_repo_"+pr.HeadBranch, "head_repo/"+pr.HeadBranch); err != nil { + return fmt.Errorf("git checkout: %s", stderr) + } + // Rebase before merging + if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), + "git", "rebase", "-q", pr.BaseBranch); err != nil { + return fmt.Errorf("git rebase [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) + } + // Checkout base branch again + if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath), + "git", "checkout", pr.BaseBranch); err != nil { + return fmt.Errorf("git checkout: %s", stderr) + } + // Merge fast forward + if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath), + "git", "merge", "--ff-only", "-q", "head_repo_"+pr.HeadBranch); err != nil { + return fmt.Errorf("git merge --ff-only [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) + } + case MergeStyleSquash: + // Merge with squash + if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath), + "git", "merge", "-q", "--squash", "head_repo/"+pr.HeadBranch); err != nil { + return fmt.Errorf("git merge --squash [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr) + } + sig := pr.Issue.Poster.NewGitSig() + if _, stderr, err = process.GetManager().ExecDir(-1, tmpBasePath, + fmt.Sprintf("PullRequest.Merge (git squash): %s", tmpBasePath), + "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), + "-m", message); err != nil { + return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr) + } + default: + return ErrInvalidMergeStyle{pr.BaseRepo.ID, mergeStyle} } // Push back to upstream. @@ -327,6 +417,9 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error log.Error(4, "MergePullRequestAction [%d]: %v", pr.ID, err) } + // Reset cached commit count + cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true)) + // Reload pull request information. if err = pr.LoadAttributes(); err != nil { log.Error(4, "LoadAttributes: %v", err) @@ -349,7 +442,6 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error return nil } - // TODO: when squash commits, no need to append merge commit. // It is possible that head branch is not fully sync with base branch for merge commits, // so we need to get latest head commit and append merge commit manually // to avoid strange diff commits produced. @@ -358,12 +450,14 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error log.Error(4, "GetBranchCommit: %v", err) return nil } - l.PushFront(mergeCommit) + if mergeStyle == MergeStyleMerge { + l.PushFront(mergeCommit) + } p := &api.PushPayload{ Ref: git.BranchPrefix + pr.BaseBranch, Before: pr.MergeBase, - After: pr.MergedCommitID, + After: mergeCommit.ID.String(), CompareURL: setting.AppURL + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID), Commits: ListToPushCommits(l).ToAPIPayloadCommits(pr.BaseRepo.HTMLURL()), Repo: pr.BaseRepo.APIFormat(AccessModeNone), @@ -563,9 +657,21 @@ func (pr *PullRequest) testPatch() (err error) { return fmt.Errorf("git read-tree --index-output=%s %s: %v - %s", indexTmpPath, pr.BaseBranch, err, stderr) } + prUnit, err := pr.BaseRepo.GetUnit(UnitTypePullRequests) + if err != nil { + return err + } + prConfig := prUnit.PullRequestsConfig() + + args := []string{"apply", "--check", "--cached"} + if prConfig.IgnoreWhitespaceConflicts { + args = append(args, "--ignore-whitespace") + } + args = append(args, patchPath) + _, stderr, err = process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("testPatch (git apply --check): %d", pr.BaseRepo.ID), []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()}, - "git", "apply", "--check", "--cached", patchPath) + "git", args...) if err != nil { for i := range patchConflicts { if strings.Contains(stderr, patchConflicts[i]) { |