Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom default merge message with .gitea/default_merge_message/<merge_style>_TEMPLATE.md #18177

Merged
merged 28 commits into from
May 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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`
lunny marked this conversation as resolved.
Show resolved Hide resolved
- `.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`
zeripath marked this conversation as resolved.
Show resolved Hide resolved

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()
lunny marked this conversation as resolved.
Show resolved Hide resolved
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)
6543 marked this conversation as resolved.
Show resolved Hide resolved
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