Skip to content

Commit

Permalink
Allow custom default merge message with .gitea/default_merge_message/…
Browse files Browse the repository at this point in the history
…<merge_style>_TEMPLATE.md (#18177)

* Allow custom default merge message with .gitea/MERGE_MESSAGE_<merge_style>_TEMPLATE.md

* Some improvements

* Follow some advices

* Fix bug

* Fix bug

* Fix lint

* Fix close comment

* Fix test

* Fix and docs

* Improve codes

* Update docs and remove unnecessary variables

* return error for GetDefaultMergeMessage

* Fix test

* improve code

* ignore unknow unit type

* return error for GetDefaultMergeMessage

* Update services/pull/merge.go

* Some improvements

* Follow some advices

* Fix bug

* Fix lint

* Improve codes

* Update docs and remove unnecessary variables

* return error for GetDefaultMergeMessage

* improve code

* Handle deleted HeadRepo in GetDefaultMergeMessage

Signed-off-by: Andrew Thornton <[email protected]>

* Fix test

* Fix test

Co-authored-by: zeripath <[email protected]>
  • Loading branch information
lunny and zeripath authored May 8, 2022
1 parent 5ca224a commit 4344a64
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 165 deletions.
33 changes: 33 additions & 0 deletions docs/content/doc/usage/issue-pull-request-templates.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions integrations/pull_merge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package integrations

import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
43 changes: 0 additions & 43 deletions models/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
53 changes: 0 additions & 53 deletions models/pull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
}
6 changes: 3 additions & 3 deletions models/repo/repo_unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
}
}
}
Expand Down
30 changes: 30 additions & 0 deletions modules/git/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"strings"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)

// Commit represents a git commit.
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 18 additions & 6 deletions routers/api/v1/repo/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) {
Expand Down
36 changes: 25 additions & 11 deletions routers/web/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 4344a64

Please sign in to comment.