summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/content/doc/usage/issue-pull-request-templates.en-us.md33
-rw-r--r--integrations/pull_merge_test.go7
-rw-r--r--models/pull.go43
-rw-r--r--models/pull_test.go53
-rw-r--r--models/repo/repo_unit.go6
-rw-r--r--modules/git/commit.go30
-rw-r--r--routers/api/v1/repo/pull.go24
-rw-r--r--routers/web/repo/issue.go36
-rw-r--r--routers/web/repo/pull.go19
-rw-r--r--services/automerge/automerge.go2
-rw-r--r--services/forms/repo_form.go26
-rw-r--r--services/pull/merge.go109
-rw-r--r--services/pull/pull_test.go60
-rw-r--r--templates/repo/issue/view_content/pull.tmpl9
14 files changed, 292 insertions, 165 deletions
diff --git a/docs/content/doc/usage/issue-pull-request-templates.en-us.md b/docs/content/doc/usage/issue-pull-request-templates.en-us.md
index 218b8a3642..65af260c31 100644
--- a/docs/content/doc/usage/issue-pull-request-templates.en-us.md
+++ b/docs/content/doc/usage/issue-pull-request-templates.en-us.md
@@ -43,6 +43,39 @@ Possible file names for PR templates:
- `.github/PULL_REQUEST_TEMPLATE.md`
- `.github/pull_request_template.md`
+Possible file names for PR default merge message templates:
+
+- `.gitea/default_merge_message/MERGE_TEMPLATE.md`
+- `.gitea/default_merge_message/REBASE_TEMPLATE.md`
+- `.gitea/default_merge_message/REBASE-MERGE_TEMPLATE.md`
+- `.gitea/default_merge_message/SQUASH_TEMPLATE.md`
+- `.gitea/default_merge_message/MANUALLY-MERGED_TEMPLATE.md`
+- `.gitea/default_merge_message/REBASE-UPDATE-ONLY_TEMPLATE.md`
+
+Possible file names for PR default merge message templates:
+
+- `.gitea/default_merge_message/MERGE_TEMPLATE.md`
+- `.gitea/default_merge_message/REBASE_TEMPLATE.md`
+- `.gitea/default_merge_message/REBASE-MERGE_TEMPLATE.md`
+- `.gitea/default_merge_message/SQUASH_TEMPLATE.md`
+- `.gitea/default_merge_message/MANUALLY-MERGED_TEMPLATE.md`
+- `.gitea/default_merge_message/REBASE-UPDATE-ONLY_TEMPLATE.md`
+
+You can use the following variables enclosed in `${}` inside these templates which follow [os.Expand](https://pkg.go.dev/os#Expand) syntax:
+
+- BaseRepoOwnerName: Base repository owner name of this pull request
+- BaseRepoName: Base repository name of this pull request
+- BaseBranch: Base repository target branch name of this pull request
+- HeadRepoOwnerName: Head repository owner name of this pull request
+- HeadRepoName: Head repository name of this pull request
+- HeadBranch: Head repository branch name of this pull request
+- PullRequestTitle: Pull request's title
+- PullRequestDescription: Pull request's description
+- PullRequestPosterName: Pull request's poster name
+- PullRequestIndex: Pull request's index number
+- PullRequestReference: Pull request's reference char with index number. i.e. #1, !2
+- ClosingIssues: return a string contains all issues which will be closed by this pull request i.e. `close #1, close #2`
+
Additionally, the New Issue page URL can be suffixed with `?title=Issue+Title&body=Issue+Text` and the form will be populated with those strings. Those strings will be used instead of the template if there is one.
## Issue Template Directory
diff --git a/integrations/pull_merge_test.go b/integrations/pull_merge_test.go
index 476f7ab52f..c50913383c 100644
--- a/integrations/pull_merge_test.go
+++ b/integrations/pull_merge_test.go
@@ -6,6 +6,7 @@ package integrations
import (
"bytes"
+ "context"
"fmt"
"net/http"
"net/http/httptest"
@@ -243,11 +244,11 @@ func TestCantMergeConflict(t *testing.T) {
gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name))
assert.NoError(t, err)
- err = pull.Merge(pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "CONFLICT")
+ err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "CONFLICT")
assert.Error(t, err, "Merge should return an error due to conflict")
assert.True(t, models.IsErrMergeConflicts(err), "Merge error is not a conflict error")
- err = pull.Merge(pr, user1, gitRepo, repo_model.MergeStyleRebase, "", "CONFLICT")
+ err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleRebase, "", "CONFLICT")
assert.Error(t, err, "Merge should return an error due to conflict")
assert.True(t, models.IsErrRebaseConflicts(err), "Merge error is not a conflict error")
gitRepo.Close()
@@ -342,7 +343,7 @@ func TestCantMergeUnrelated(t *testing.T) {
BaseBranch: "base",
}).(*models.PullRequest)
- err = pull.Merge(pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "UNRELATED")
+ err = pull.Merge(context.Background(), pr, user1, gitRepo, repo_model.MergeStyleMerge, "", "UNRELATED")
assert.Error(t, err, "Merge should return an error due to unrelated")
assert.True(t, models.IsErrMergeUnrelatedHistories(err), "Merge error is not a unrelated histories error")
gitRepo.Close()
diff --git a/models/pull.go b/models/pull.go
index 0fa3bdf14f..fc5c0d61b3 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
@@ -228,34 +227,6 @@ func (pr *PullRequest) LoadProtectedBranchCtx(ctx context.Context) (err error) {
return
}
-// GetDefaultMergeMessage returns default message used when merging pull request
-func (pr *PullRequest) GetDefaultMergeMessage(ctx context.Context) (string, error) {
- if pr.HeadRepo == nil {
- var err error
- pr.HeadRepo, err = repo_model.GetRepositoryByIDCtx(ctx, pr.HeadRepoID)
- if err != nil {
- return "", fmt.Errorf("GetRepositoryById[%d]: %v", pr.HeadRepoID, err)
- }
- }
- if err := pr.LoadIssueCtx(ctx); err != nil {
- return "", fmt.Errorf("Cannot load issue %d for PR id %d: Error: %v", pr.IssueID, pr.ID, err)
- }
- if err := pr.LoadBaseRepoCtx(ctx); err != nil {
- return "", fmt.Errorf("LoadBaseRepo: %v", err)
- }
-
- issueReference := "#"
- if pr.BaseRepo.UnitEnabledCtx(ctx, unit.TypeExternalTracker) {
- issueReference = "!"
- }
-
- if pr.BaseRepoID == pr.HeadRepoID {
- return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), nil
- }
-
- return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch), nil
-}
-
// ReviewCount represents a count of Reviews
type ReviewCount struct {
IssueID int64
@@ -338,20 +309,6 @@ func (pr *PullRequest) getReviewedByLines(writer io.Writer) error {
return committer.Commit()
}
-// GetDefaultSquashMessage returns default message used when squash and merging pull request
-func (pr *PullRequest) GetDefaultSquashMessage(ctx context.Context) (string, error) {
- if err := pr.LoadIssueCtx(ctx); err != nil {
- return "", fmt.Errorf("LoadIssue: %v", err)
- }
- if err := pr.LoadBaseRepoCtx(ctx); err != nil {
- return "", fmt.Errorf("LoadBaseRepo: %v", err)
- }
- if pr.BaseRepo.UnitEnabledCtx(ctx, unit.TypeExternalTracker) {
- return fmt.Sprintf("%s (!%d)", pr.Issue.Title, pr.Issue.Index), nil
- }
- return fmt.Sprintf("%s (#%d)", pr.Issue.Title, pr.Issue.Index), nil
-}
-
// GetGitRefName returns git ref for hidden pull request branch
func (pr *PullRequest) GetGitRefName() string {
return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index)
diff --git a/models/pull_test.go b/models/pull_test.go
index 92cf9a6524..6119bca692 100644
--- a/models/pull_test.go
+++ b/models/pull_test.go
@@ -8,10 +8,7 @@ import (
"testing"
"code.gitea.io/gitea/models/db"
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
- user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
)
@@ -256,53 +253,3 @@ func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) {
pr.Issue.Title = "[wip] " + original
assert.Equal(t, "[wip]", pr.GetWorkInProgressPrefix())
}
-
-func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest)
-
- msg, err := pr.GetDefaultMergeMessage(db.DefaultContext)
- assert.NoError(t, err)
- assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", msg)
-
- pr.BaseRepoID = 1
- pr.HeadRepoID = 2
- msg, err = pr.GetDefaultMergeMessage(db.DefaultContext)
- assert.NoError(t, err)
- assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", msg)
-}
-
-func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- externalTracker := repo_model.RepoUnit{
- Type: unit.TypeExternalTracker,
- Config: &repo_model.ExternalTrackerConfig{
- ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
- },
- }
- baseRepo := &repo_model.Repository{Name: "testRepo", ID: 1}
- baseRepo.Owner = &user_model.User{Name: "testOwner"}
- baseRepo.Units = []*repo_model.RepoUnit{&externalTracker}
-
- pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2, BaseRepo: baseRepo}).(*PullRequest)
-
- msg, err := pr.GetDefaultMergeMessage(db.DefaultContext)
- assert.NoError(t, err)
- assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", msg)
-
- pr.BaseRepoID = 1
- pr.HeadRepoID = 2
- msg, err = pr.GetDefaultMergeMessage(db.DefaultContext)
- assert.NoError(t, err)
- assert.Equal(t, "Merge pull request 'issue3' (!3) from user2/repo1:branch2 into master", msg)
-}
-
-func TestPullRequest_GetDefaultSquashMessage(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- pr := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest)
-
- msg, err := pr.GetDefaultSquashMessage(db.DefaultContext)
- assert.NoError(t, err)
- assert.Equal(t, "issue3 (#3)", msg)
-}
diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go
index 37f1c70545..4c87f017b3 100644
--- a/models/repo/repo_unit.go
+++ b/models/repo/repo_unit.go
@@ -173,8 +173,6 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
switch colName {
case "type":
switch unit.Type(db.Cell2Int64(val)) {
- case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects:
- r.Config = new(UnitConfig)
case unit.TypeExternalWiki:
r.Config = new(ExternalWikiConfig)
case unit.TypeExternalTracker:
@@ -183,8 +181,10 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
r.Config = new(PullRequestsConfig)
case unit.TypeIssues:
r.Config = new(IssuesConfig)
+ case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects:
+ fallthrough
default:
- panic(fmt.Sprintf("unrecognized repo unit type: %v", *val))
+ r.Config = new(UnitConfig)
}
}
}
diff --git a/modules/git/commit.go b/modules/git/commit.go
index 8337e54fef..8c194ef502 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -17,6 +17,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/util"
)
// Commit represents a git commit.
@@ -306,6 +307,35 @@ func (c *Commit) HasFile(filename string) (bool, error) {
return true, nil
}
+// GetFileContent reads a file content as a string or returns false if this was not possible
+func (c *Commit) GetFileContent(filename string, limit int) (string, error) {
+ entry, err := c.GetTreeEntryByPath(filename)
+ if err != nil {
+ return "", err
+ }
+
+ r, err := entry.Blob().DataAsync()
+ if err != nil {
+ return "", err
+ }
+ defer r.Close()
+
+ if limit > 0 {
+ bs := make([]byte, limit)
+ n, err := util.ReadAtMost(r, bs)
+ if err != nil {
+ return "", err
+ }
+ return string(bs[:n]), nil
+ }
+
+ bytes, err := io.ReadAll(r)
+ if err != nil {
+ return "", err
+ }
+ return string(bytes), nil
+}
+
// GetSubModules get all the sub modules of current revision git tree
func (c *Commit) GetSubModules() (*ObjectCache, error) {
if c.submoduleCache != nil {
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index 91bb57f3fd..ecf96ea0c2 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -801,14 +801,26 @@ func MergePullRequest(ctx *context.APIContext) {
return
}
- // set defaults to propagate needed fields
- if err := form.SetDefaults(ctx, pr); err != nil {
- ctx.ServerError("SetDefaults", fmt.Errorf("SetDefaults: %v", err))
- return
+ if len(form.Do) == 0 {
+ form.Do = string(repo_model.MergeStyleMerge)
+ }
+
+ message := strings.TrimSpace(form.MergeTitleField)
+ if len(message) == 0 {
+ message, err = pull_service.GetDefaultMergeMessage(ctx.Repo.GitRepo, pr, repo_model.MergeStyle(form.Do))
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetDefaultMergeMessage", err)
+ return
+ }
+ }
+
+ form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
+ if len(form.MergeMessageField) > 0 {
+ message += "\n\n" + form.MergeMessageField
}
if form.MergeWhenChecksSucceed {
- scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), form.MergeTitleField)
+ scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message)
if err != nil {
if pull_model.IsErrAlreadyScheduledToAutoMerge(err) {
ctx.Error(http.StatusConflict, "ScheduleAutoMerge", err)
@@ -823,7 +835,7 @@ func MergePullRequest(ctx *context.APIContext) {
}
}
- if err := pull_service.Merge(pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, form.MergeTitleField); err != nil {
+ if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message); err != nil {
if models.IsErrInvalidMergeStyle(err) {
ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))
} else if models.IsErrMergeConflicts(err) {
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 620b76f46d..7ddeb05f71 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -712,8 +712,6 @@ func RetrieveRepoMetas(ctx *context.Context, repo *repo_model.Repository, isPull
}
func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (string, bool) {
- var bytes []byte
-
if ctx.Repo.Commit == nil {
var err error
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
@@ -734,7 +732,7 @@ func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (str
return "", false
}
defer r.Close()
- bytes, err = io.ReadAll(r)
+ bytes, err := io.ReadAll(r)
if err != nil {
return "", false
}
@@ -1574,26 +1572,42 @@ func ViewIssue(ctx *context.Context) {
}
prConfig := prUnit.PullRequestsConfig()
+ var mergeStyle repo_model.MergeStyle
// Check correct values and select default
if ms, ok := ctx.Data["MergeStyle"].(repo_model.MergeStyle); !ok ||
!prConfig.IsMergeStyleAllowed(ms) {
defaultMergeStyle := prConfig.GetDefaultMergeStyle()
if prConfig.IsMergeStyleAllowed(defaultMergeStyle) && !ok {
- ctx.Data["MergeStyle"] = defaultMergeStyle
+ mergeStyle = defaultMergeStyle
} else if prConfig.AllowMerge {
- ctx.Data["MergeStyle"] = repo_model.MergeStyleMerge
+ mergeStyle = repo_model.MergeStyleMerge
} else if prConfig.AllowRebase {
- ctx.Data["MergeStyle"] = repo_model.MergeStyleRebase
+ mergeStyle = repo_model.MergeStyleRebase
} else if prConfig.AllowRebaseMerge {
- ctx.Data["MergeStyle"] = repo_model.MergeStyleRebaseMerge
+ mergeStyle = repo_model.MergeStyleRebaseMerge
} else if prConfig.AllowSquash {
- ctx.Data["MergeStyle"] = repo_model.MergeStyleSquash
+ mergeStyle = repo_model.MergeStyleSquash
} else if prConfig.AllowManualMerge {
- ctx.Data["MergeStyle"] = repo_model.MergeStyleManuallyMerged
- } else {
- ctx.Data["MergeStyle"] = ""
+ mergeStyle = repo_model.MergeStyleManuallyMerged
}
}
+
+ ctx.Data["MergeStyle"] = mergeStyle
+
+ defaultMergeMessage, err := pull_service.GetDefaultMergeMessage(ctx.Repo.GitRepo, pull, mergeStyle)
+ if err != nil {
+ ctx.ServerError("GetDefaultMergeMessage", err)
+ return
+ }
+ ctx.Data["DefaultMergeMessage"] = defaultMergeMessage
+
+ defaultSquashMergeMessage, err := pull_service.GetDefaultMergeMessage(ctx.Repo.GitRepo, pull, repo_model.MergeStyleSquash)
+ if err != nil {
+ ctx.ServerError("GetDefaultSquashMergeMessage", err)
+ return
+ }
+ ctx.Data["DefaultSquashMergeMessage"] = defaultSquashMergeMessage
+
if err = pull.LoadProtectedBranch(); err != nil {
ctx.ServerError("LoadProtectedBranch", err)
return
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index a0b3567738..27b61309a5 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -950,13 +950,22 @@ func MergePullRequest(ctx *context.Context) {
return
}
- // set defaults to propagate needed fields
- if err := form.SetDefaults(ctx, pr); err != nil {
- ctx.ServerError("SetDefaults", fmt.Errorf("SetDefaults: %v", err))
- return
+ message := strings.TrimSpace(form.MergeTitleField)
+ if len(message) == 0 {
+ var err error
+ message, err = pull_service.GetDefaultMergeMessage(ctx.Repo.GitRepo, pr, repo_model.MergeStyle(form.Do))
+ if err != nil {
+ ctx.ServerError("GetDefaultMergeMessage", err)
+ return
+ }
+ }
+
+ form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
+ if len(form.MergeMessageField) > 0 {
+ message += "\n\n" + form.MergeMessageField
}
- if err := pull_service.Merge(pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, form.MergeTitleField); err != nil {
+ if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message); err != nil {
if models.IsErrInvalidMergeStyle(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
ctx.Redirect(issue.Link())
diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go
index 389546ed57..e098f2cec0 100644
--- a/services/automerge/automerge.go
+++ b/services/automerge/automerge.go
@@ -234,7 +234,7 @@ func handlePull(pullID int64, sha string) {
defer baseGitRepo.Close()
}
- if err := pull_service.Merge(pr, doer, baseGitRepo, scheduledPRM.MergeStyle, "", scheduledPRM.Message); err != nil {
+ if err := pull_service.Merge(ctx, pr, doer, baseGitRepo, scheduledPRM.MergeStyle, "", scheduledPRM.Message); err != nil {
log.Error("pull_service.Merge: %v", err)
return
}
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index bacee9a13c..6c61ed00ec 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -6,7 +6,6 @@
package forms
import (
- stdContext "context"
"net/http"
"net/url"
"strings"
@@ -602,31 +601,6 @@ func (f *MergePullRequestForm) Validate(req *http.Request, errs binding.Errors)
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
}
-// SetDefaults if not provided for mergestyle and commit message
-func (f *MergePullRequestForm) SetDefaults(ctx stdContext.Context, pr *models.PullRequest) (err error) {
- if f.Do == "" {
- f.Do = "merge"
- }
-
- f.MergeTitleField = strings.TrimSpace(f.MergeTitleField)
- if len(f.MergeTitleField) == 0 {
- switch f.Do {
- case "merge", "rebase-merge":
- f.MergeTitleField, err = pr.GetDefaultMergeMessage(ctx)
- case "squash":
- f.MergeTitleField, err = pr.GetDefaultSquashMessage(ctx)
- }
- }
-
- f.MergeMessageField = strings.TrimSpace(f.MergeMessageField)
- if len(f.MergeMessageField) > 0 {
- f.MergeTitleField += "\n\n" + f.MergeMessageField
- f.MergeMessageField = ""
- }
-
- return
-}
-
// CodeCommentForm form for adding code comments for PRs
type CodeCommentForm struct {
Origin string `binding:"Required;In(timeline,diff)"`
diff --git a/services/pull/merge.go b/services/pull/merge.go
index 8cc4d88888..b14abcd780 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -13,6 +13,7 @@ import (
"os"
"path/filepath"
"regexp"
+ "strconv"
"strings"
"time"
@@ -33,9 +34,101 @@ import (
issue_service "code.gitea.io/gitea/services/issue"
)
+// GetDefaultMergeMessage returns default message used when merging pull request
+func GetDefaultMergeMessage(baseGitRepo *git.Repository, pr *models.PullRequest, mergeStyle repo_model.MergeStyle) (string, error) {
+ if err := pr.LoadHeadRepo(); err != nil {
+ return "", err
+ }
+ if err := pr.LoadBaseRepo(); err != nil {
+ return "", err
+ }
+ if pr.BaseRepo == nil {
+ return "", repo_model.ErrRepoNotExist{ID: pr.BaseRepoID}
+ }
+
+ if err := pr.LoadIssue(); err != nil {
+ return "", err
+ }
+
+ isExternalTracker := pr.BaseRepo.UnitEnabled(unit.TypeExternalTracker)
+ issueReference := "#"
+ if isExternalTracker {
+ issueReference = "!"
+ }
+
+ if mergeStyle != "" {
+ templateFilepath := fmt.Sprintf(".gitea/default_merge_message/%s_TEMPLATE.md", strings.ToUpper(string(mergeStyle)))
+ commit, err := baseGitRepo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
+ if err != nil {
+ return "", err
+ }
+ templateContent, err := commit.GetFileContent(templateFilepath, setting.Repository.PullRequest.DefaultMergeMessageSize)
+ if err != nil {
+ if !git.IsErrNotExist(err) {
+ return "", err
+ }
+ } else {
+ vars := map[string]string{
+ "BaseRepoOwnerName": pr.BaseRepo.OwnerName,
+ "BaseRepoName": pr.BaseRepo.Name,
+ "BaseBranch": pr.BaseBranch,
+ "HeadRepoOwnerName": "",
+ "HeadRepoName": "",
+ "HeadBranch": pr.HeadBranch,
+ "PullRequestTitle": pr.Issue.Title,
+ "PullRequestDescription": pr.Issue.Content,
+ "PullRequestPosterName": pr.Issue.Poster.Name,
+ "PullRequestIndex": strconv.FormatInt(pr.Index, 10),
+ "PullRequestReference": fmt.Sprintf("%s%d", issueReference, pr.Index),
+ }
+ if pr.HeadRepo != nil {
+ vars["HeadRepoOwnerName"] = pr.HeadRepo.OwnerName
+ vars["HeadRepoName"] = pr.HeadRepo.Name
+ }
+ refs, err := pr.ResolveCrossReferences(baseGitRepo.Ctx)
+ if err == nil {
+ closeIssueIndexes := make([]string, 0, len(refs))
+ closeWord := "close"
+ if len(setting.Repository.PullRequest.CloseKeywords) > 0 {
+ closeWord = setting.Repository.PullRequest.CloseKeywords[0]
+ }
+ for _, ref := range refs {
+ if ref.RefAction == references.XRefActionCloses {
+ closeIssueIndexes = append(closeIssueIndexes, fmt.Sprintf("%s %s%d", closeWord, issueReference, ref.Issue.Index))
+ }
+ }
+ if len(closeIssueIndexes) > 0 {
+ vars["ClosingIssues"] = strings.Join(closeIssueIndexes, ", ")
+ } else {
+ vars["ClosingIssues"] = ""
+ }
+ }
+
+ return os.Expand(templateContent, func(s string) string {
+ return vars[s]
+ }), nil
+ }
+ }
+
+ // Squash merge has a different from other styles.
+ if mergeStyle == repo_model.MergeStyleSquash {
+ return fmt.Sprintf("%s (%s%d)", pr.Issue.Title, issueReference, pr.Issue.Index), nil
+ }
+
+ if pr.BaseRepoID == pr.HeadRepoID {
+ return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), nil
+ }
+
+ if pr.HeadRepo == nil {
+ return fmt.Sprintf("Merge pull request '%s' (%s%d) from <deleted>:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadBranch, pr.BaseBranch), nil
+ }
+
+ return fmt.Sprintf("Merge pull request '%s' (%s%d) from %s:%s into %s", pr.Issue.Title, issueReference, pr.Issue.Index, pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseBranch), nil
+}
+
// Merge merges pull request to base repository.
// Caller should check PR is ready to be merged (review and status checks)
-func Merge(pr *models.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string) error {
+func Merge(ctx context.Context, pr *models.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string) error {
if err := pr.LoadHeadRepo(); err != nil {
log.Error("LoadHeadRepo: %v", err)
return fmt.Errorf("LoadHeadRepo: %v", err)
@@ -79,18 +172,18 @@ func Merge(pr *models.PullRequest, doer *user_model.User, baseGitRepo *git.Repos
pr.Merger = doer
pr.MergerID = doer.ID
- if _, err := pr.SetMerged(db.DefaultContext); err != nil {
+ if _, err := pr.SetMerged(ctx); err != nil {
log.Error("setMerged [%d]: %v", pr.ID, err)
}
- if err := pr.LoadIssueCtx(db.DefaultContext); err != nil {
+ if err := pr.LoadIssueCtx(ctx); err != nil {
log.Error("loadIssue [%d]: %v", pr.ID, err)
}
- if err := pr.Issue.LoadRepo(db.DefaultContext); err != nil {
+ if err := pr.Issue.LoadRepo(ctx); err != nil {
log.Error("loadRepo for issue [%d]: %v", pr.ID, err)
}
- if err := pr.Issue.Repo.GetOwner(db.DefaultContext); err != nil {
+ if err := pr.Issue.Repo.GetOwner(ctx); err != nil {
log.Error("GetOwner for issue repo [%d]: %v", pr.ID, err)
}
@@ -100,17 +193,17 @@ func Merge(pr *models.PullRequest, doer *user_model.User, baseGitRepo *git.Repos
cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true))
// Resolve cross references
- refs, err := pr.ResolveCrossReferences(db.DefaultContext)
+ refs, err := pr.ResolveCrossReferences(ctx)
if err != nil {
log.Error("ResolveCrossReferences: %v", err)
return nil
}
for _, ref := range refs {
- if err = ref.LoadIssueCtx(db.DefaultContext); err != nil {
+ if err = ref.LoadIssueCtx(ctx); err != nil {
return err
}
- if err = ref.Issue.LoadRepo(db.DefaultContext); err != nil {
+ if err = ref.Issue.LoadRepo(ctx); err != nil {
return err
}
close := ref.RefAction == references.XRefActionCloses
diff --git a/services/pull/pull_test.go b/services/pull/pull_test.go
index 81627ebb77..09bae97780 100644
--- a/services/pull/pull_test.go
+++ b/services/pull/pull_test.go
@@ -8,6 +8,12 @@ package pull
import (
"testing"
+ "code.gitea.io/gitea/models"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unit"
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/git"
+
"github.com/stretchr/testify/assert"
)
@@ -29,3 +35,57 @@ func TestPullRequest_CommitMessageTrailersPattern(t *testing.T) {
assert.True(t, commitMessageTrailersPattern.MatchString("Additional whitespace is accepted.\n\nSigned-off-by \t : \tBob <bob@example.com> "))
assert.True(t, commitMessageTrailersPattern.MatchString("Folded value.\n\nFolded-trailer: This is\n a folded\n trailer value\nOther-Trailer: Value"))
}
+
+func TestPullRequest_GetDefaultMergeMessage_InternalTracker(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ pr := unittest.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest)
+
+ assert.NoError(t, pr.LoadBaseRepo())
+ gitRepo, err := git.OpenRepository(git.DefaultContext, pr.BaseRepo.RepoPath())
+ assert.NoError(t, err)
+ defer gitRepo.Close()
+
+ mergeMessage, err := GetDefaultMergeMessage(gitRepo, pr, "")
+ assert.NoError(t, err)
+ assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", mergeMessage)
+
+ pr.BaseRepoID = 1
+ pr.HeadRepoID = 2
+ mergeMessage, err = GetDefaultMergeMessage(gitRepo, pr, "")
+ assert.NoError(t, err)
+ assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo1:branch2 into master", mergeMessage)
+}
+
+func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ externalTracker := repo_model.RepoUnit{
+ Type: unit.TypeExternalTracker,
+ Config: &repo_model.ExternalTrackerConfig{
+ ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
+ },
+ }
+ baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+ baseRepo.Units = []*repo_model.RepoUnit{&externalTracker}
+
+ pr := unittest.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2, BaseRepo: baseRepo}).(*models.PullRequest)
+
+ assert.NoError(t, pr.LoadBaseRepo())
+ gitRepo, err := git.OpenRepository(git.DefaultContext, pr.BaseRepo.RepoPath())
+ assert.NoError(t, err)
+ defer gitRepo.Close()
+
+ mergeMessage, err := GetDefaultMergeMessage(gitRepo, pr, "")
+ assert.NoError(t, err)
+
+ assert.Equal(t, "Merge pull request 'issue3' (!3) from branch2 into master", mergeMessage)
+
+ pr.BaseRepoID = 1
+ pr.HeadRepoID = 2
+ pr.BaseRepo = nil
+ pr.HeadRepo = nil
+ mergeMessage, err = GetDefaultMergeMessage(gitRepo, pr, "")
+ assert.NoError(t, err)
+
+ assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo2:branch2 into master", mergeMessage)
+}
diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl
index 8c43ace32e..07b2e89d24 100644
--- a/templates/repo/issue/view_content/pull.tmpl
+++ b/templates/repo/issue/view_content/pull.tmpl
@@ -329,7 +329,7 @@
{{.CsrfTokenHtml}}
<input type="hidden" name="head_commit_id" value="{{.PullHeadCommitID}}">
<div class="field">
- <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage $.Context}}">
+ <input type="text" name="merge_title_field" value="{{.DefaultMergeMessage}}">
</div>
<div class="field">
<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">Reviewed-on: {{$.Issue.HTMLURL}}&#13;&#10;{{$approvers}}</textarea>
@@ -375,10 +375,7 @@
{{.CsrfTokenHtml}}
<input type="hidden" name="head_commit_id" value="{{.PullHeadCommitID}}">
<div class="field">
- <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage $.Context}}">
- </div>
- <div class="field">
- <textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">Reviewed-on: {{$.Issue.HTMLURL}}&#13;&#10;{{$approvers}}</textarea>
+ <input type="text" name="merge_title_field" value="{{.DefaultMergeMessage}}">
</div>
<button class="ui green button" type="submit" name="do" value="rebase-merge">
{{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}}
@@ -401,7 +398,7 @@
{{.CsrfTokenHtml}}
<input type="hidden" name="head_commit_id" value="{{.PullHeadCommitID}}">
<div class="field">
- <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultSquashMessage $.Context}}">
+ <input type="text" name="merge_title_field" value="{{.DefaultSquashMergeMessage}}">
</div>
<div class="field">
<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">{{.GetCommitMessages}}Reviewed-on: {{$.Issue.HTMLURL}}&#13;&#10;{{$approvers}}</textarea>