mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Allow custom default merge message with .gitea/default_merge_message/<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 <art27@cantab.net> * Fix test * Fix test Co-authored-by: zeripath <art27@cantab.net>
This commit is contained in:
		| @@ -43,6 +43,39 @@ Possible file names for PR templates: | |||||||
| - `.github/PULL_REQUEST_TEMPLATE.md` | - `.github/PULL_REQUEST_TEMPLATE.md` | ||||||
| - `.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. | 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 | ## Issue Template Directory | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ package integrations | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"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)) | 		gitRepo, err := git.OpenRepository(git.DefaultContext, repo_model.RepoPath(user1.Name, repo1.Name)) | ||||||
| 		assert.NoError(t, err) | 		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.Error(t, err, "Merge should return an error due to conflict") | ||||||
| 		assert.True(t, models.IsErrMergeConflicts(err), "Merge error is not a conflict error") | 		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.Error(t, err, "Merge should return an error due to conflict") | ||||||
| 		assert.True(t, models.IsErrRebaseConflicts(err), "Merge error is not a conflict error") | 		assert.True(t, models.IsErrRebaseConflicts(err), "Merge error is not a conflict error") | ||||||
| 		gitRepo.Close() | 		gitRepo.Close() | ||||||
| @@ -342,7 +343,7 @@ func TestCantMergeUnrelated(t *testing.T) { | |||||||
| 			BaseBranch: "base", | 			BaseBranch: "base", | ||||||
| 		}).(*models.PullRequest) | 		}).(*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.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") | 		assert.True(t, models.IsErrMergeUnrelatedHistories(err), "Merge error is not a unrelated histories error") | ||||||
| 		gitRepo.Close() | 		gitRepo.Close() | ||||||
|   | |||||||
| @@ -13,7 +13,6 @@ import ( | |||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models/db" | 	"code.gitea.io/gitea/models/db" | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	"code.gitea.io/gitea/models/unit" |  | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| @@ -228,34 +227,6 @@ func (pr *PullRequest) LoadProtectedBranchCtx(ctx context.Context) (err error) { | |||||||
| 	return | 	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 | // ReviewCount represents a count of Reviews | ||||||
| type ReviewCount struct { | type ReviewCount struct { | ||||||
| 	IssueID int64 | 	IssueID int64 | ||||||
| @@ -338,20 +309,6 @@ func (pr *PullRequest) getReviewedByLines(writer io.Writer) error { | |||||||
| 	return committer.Commit() | 	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 | // GetGitRefName returns git ref for hidden pull request branch | ||||||
| func (pr *PullRequest) GetGitRefName() string { | func (pr *PullRequest) GetGitRefName() string { | ||||||
| 	return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index) | 	return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index) | ||||||
|   | |||||||
| @@ -8,10 +8,7 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models/db" | 	"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" | 	"code.gitea.io/gitea/models/unittest" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" |  | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
| @@ -256,53 +253,3 @@ func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) { | |||||||
| 	pr.Issue.Title = "[wip] " + original | 	pr.Issue.Title = "[wip] " + original | ||||||
| 	assert.Equal(t, "[wip]", pr.GetWorkInProgressPrefix()) | 	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) |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -173,8 +173,6 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { | |||||||
| 	switch colName { | 	switch colName { | ||||||
| 	case "type": | 	case "type": | ||||||
| 		switch unit.Type(db.Cell2Int64(val)) { | 		switch unit.Type(db.Cell2Int64(val)) { | ||||||
| 		case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects: |  | ||||||
| 			r.Config = new(UnitConfig) |  | ||||||
| 		case unit.TypeExternalWiki: | 		case unit.TypeExternalWiki: | ||||||
| 			r.Config = new(ExternalWikiConfig) | 			r.Config = new(ExternalWikiConfig) | ||||||
| 		case unit.TypeExternalTracker: | 		case unit.TypeExternalTracker: | ||||||
| @@ -183,8 +181,10 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { | |||||||
| 			r.Config = new(PullRequestsConfig) | 			r.Config = new(PullRequestsConfig) | ||||||
| 		case unit.TypeIssues: | 		case unit.TypeIssues: | ||||||
| 			r.Config = new(IssuesConfig) | 			r.Config = new(IssuesConfig) | ||||||
|  | 		case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects: | ||||||
|  | 			fallthrough | ||||||
| 		default: | 		default: | ||||||
| 			panic(fmt.Sprintf("unrecognized repo unit type: %v", *val)) | 			r.Config = new(UnitConfig) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/util" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Commit represents a git commit. | // Commit represents a git commit. | ||||||
| @@ -306,6 +307,35 @@ func (c *Commit) HasFile(filename string) (bool, error) { | |||||||
| 	return true, nil | 	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 | // GetSubModules get all the sub modules of current revision git tree | ||||||
| func (c *Commit) GetSubModules() (*ObjectCache, error) { | func (c *Commit) GetSubModules() (*ObjectCache, error) { | ||||||
| 	if c.submoduleCache != nil { | 	if c.submoduleCache != nil { | ||||||
|   | |||||||
| @@ -801,14 +801,26 @@ func MergePullRequest(ctx *context.APIContext) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// set defaults to propagate needed fields | 	if len(form.Do) == 0 { | ||||||
| 	if err := form.SetDefaults(ctx, pr); err != nil { | 		form.Do = string(repo_model.MergeStyleMerge) | ||||||
| 		ctx.ServerError("SetDefaults", fmt.Errorf("SetDefaults: %v", err)) | 	} | ||||||
| 		return |  | ||||||
|  | 	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 { | 	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 err != nil { | ||||||
| 			if pull_model.IsErrAlreadyScheduledToAutoMerge(err) { | 			if pull_model.IsErrAlreadyScheduledToAutoMerge(err) { | ||||||
| 				ctx.Error(http.StatusConflict, "ScheduleAutoMerge", 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) { | 		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))) | 			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) { | 		} else if models.IsErrMergeConflicts(err) { | ||||||
|   | |||||||
| @@ -712,8 +712,6 @@ func RetrieveRepoMetas(ctx *context.Context, repo *repo_model.Repository, isPull | |||||||
| } | } | ||||||
|  |  | ||||||
| func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (string, bool) { | func getFileContentFromDefaultBranch(ctx *context.Context, filename string) (string, bool) { | ||||||
| 	var bytes []byte |  | ||||||
|  |  | ||||||
| 	if ctx.Repo.Commit == nil { | 	if ctx.Repo.Commit == nil { | ||||||
| 		var err error | 		var err error | ||||||
| 		ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) | 		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 | 		return "", false | ||||||
| 	} | 	} | ||||||
| 	defer r.Close() | 	defer r.Close() | ||||||
| 	bytes, err = io.ReadAll(r) | 	bytes, err := io.ReadAll(r) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", false | 		return "", false | ||||||
| 	} | 	} | ||||||
| @@ -1574,26 +1572,42 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 		} | 		} | ||||||
| 		prConfig := prUnit.PullRequestsConfig() | 		prConfig := prUnit.PullRequestsConfig() | ||||||
|  |  | ||||||
|  | 		var mergeStyle repo_model.MergeStyle | ||||||
| 		// Check correct values and select default | 		// Check correct values and select default | ||||||
| 		if ms, ok := ctx.Data["MergeStyle"].(repo_model.MergeStyle); !ok || | 		if ms, ok := ctx.Data["MergeStyle"].(repo_model.MergeStyle); !ok || | ||||||
| 			!prConfig.IsMergeStyleAllowed(ms) { | 			!prConfig.IsMergeStyleAllowed(ms) { | ||||||
| 			defaultMergeStyle := prConfig.GetDefaultMergeStyle() | 			defaultMergeStyle := prConfig.GetDefaultMergeStyle() | ||||||
| 			if prConfig.IsMergeStyleAllowed(defaultMergeStyle) && !ok { | 			if prConfig.IsMergeStyleAllowed(defaultMergeStyle) && !ok { | ||||||
| 				ctx.Data["MergeStyle"] = defaultMergeStyle | 				mergeStyle = defaultMergeStyle | ||||||
| 			} else if prConfig.AllowMerge { | 			} else if prConfig.AllowMerge { | ||||||
| 				ctx.Data["MergeStyle"] = repo_model.MergeStyleMerge | 				mergeStyle = repo_model.MergeStyleMerge | ||||||
| 			} else if prConfig.AllowRebase { | 			} else if prConfig.AllowRebase { | ||||||
| 				ctx.Data["MergeStyle"] = repo_model.MergeStyleRebase | 				mergeStyle = repo_model.MergeStyleRebase | ||||||
| 			} else if prConfig.AllowRebaseMerge { | 			} else if prConfig.AllowRebaseMerge { | ||||||
| 				ctx.Data["MergeStyle"] = repo_model.MergeStyleRebaseMerge | 				mergeStyle = repo_model.MergeStyleRebaseMerge | ||||||
| 			} else if prConfig.AllowSquash { | 			} else if prConfig.AllowSquash { | ||||||
| 				ctx.Data["MergeStyle"] = repo_model.MergeStyleSquash | 				mergeStyle = repo_model.MergeStyleSquash | ||||||
| 			} else if prConfig.AllowManualMerge { | 			} else if prConfig.AllowManualMerge { | ||||||
| 				ctx.Data["MergeStyle"] = repo_model.MergeStyleManuallyMerged | 				mergeStyle = repo_model.MergeStyleManuallyMerged | ||||||
| 			} else { |  | ||||||
| 				ctx.Data["MergeStyle"] = "" |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		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 { | 		if err = pull.LoadProtectedBranch(); err != nil { | ||||||
| 			ctx.ServerError("LoadProtectedBranch", err) | 			ctx.ServerError("LoadProtectedBranch", err) | ||||||
| 			return | 			return | ||||||
|   | |||||||
| @@ -950,13 +950,22 @@ func MergePullRequest(ctx *context.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// set defaults to propagate needed fields | 	message := strings.TrimSpace(form.MergeTitleField) | ||||||
| 	if err := form.SetDefaults(ctx, pr); err != nil { | 	if len(message) == 0 { | ||||||
| 		ctx.ServerError("SetDefaults", fmt.Errorf("SetDefaults: %v", err)) | 		var err error | ||||||
| 		return | 		message, err = pull_service.GetDefaultMergeMessage(ctx.Repo.GitRepo, pr, repo_model.MergeStyle(form.Do)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.ServerError("GetDefaultMergeMessage", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := pull_service.Merge(pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, form.MergeTitleField); err != nil { | 	form.MergeMessageField = strings.TrimSpace(form.MergeMessageField) | ||||||
|  | 	if len(form.MergeMessageField) > 0 { | ||||||
|  | 		message += "\n\n" + form.MergeMessageField | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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) { | 		if models.IsErrInvalidMergeStyle(err) { | ||||||
| 			ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) | 			ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) | ||||||
| 			ctx.Redirect(issue.Link()) | 			ctx.Redirect(issue.Link()) | ||||||
|   | |||||||
| @@ -234,7 +234,7 @@ func handlePull(pullID int64, sha string) { | |||||||
| 		defer baseGitRepo.Close() | 		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) | 		log.Error("pull_service.Merge: %v", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ | |||||||
| package forms | package forms | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	stdContext "context" |  | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -602,31 +601,6 @@ func (f *MergePullRequestForm) Validate(req *http.Request, errs binding.Errors) | |||||||
| 	return middleware.Validate(errs, ctx.Data, f, ctx.Locale) | 	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 | // CodeCommentForm form for adding code comments for PRs | ||||||
| type CodeCommentForm struct { | type CodeCommentForm struct { | ||||||
| 	Origin         string `binding:"Required;In(timeline,diff)"` | 	Origin         string `binding:"Required;In(timeline,diff)"` | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"regexp" | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| @@ -33,9 +34,101 @@ import ( | |||||||
| 	issue_service "code.gitea.io/gitea/services/issue" | 	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. | // Merge merges pull request to base repository. | ||||||
| // Caller should check PR is ready to be merged (review and status checks) | // 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 { | 	if err := pr.LoadHeadRepo(); err != nil { | ||||||
| 		log.Error("LoadHeadRepo: %v", err) | 		log.Error("LoadHeadRepo: %v", err) | ||||||
| 		return fmt.Errorf("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.Merger = doer | ||||||
| 	pr.MergerID = doer.ID | 	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) | 		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) | 		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) | 		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) | 		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)) | 	cache.Remove(pr.Issue.Repo.GetCommitsCountCacheKey(pr.BaseBranch, true)) | ||||||
|  |  | ||||||
| 	// Resolve cross references | 	// Resolve cross references | ||||||
| 	refs, err := pr.ResolveCrossReferences(db.DefaultContext) | 	refs, err := pr.ResolveCrossReferences(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error("ResolveCrossReferences: %v", err) | 		log.Error("ResolveCrossReferences: %v", err) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, ref := range refs { | 	for _, ref := range refs { | ||||||
| 		if err = ref.LoadIssueCtx(db.DefaultContext); err != nil { | 		if err = ref.LoadIssueCtx(ctx); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		if err = ref.Issue.LoadRepo(db.DefaultContext); err != nil { | 		if err = ref.Issue.LoadRepo(ctx); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		close := ref.RefAction == references.XRefActionCloses | 		close := ref.RefAction == references.XRefActionCloses | ||||||
|   | |||||||
| @@ -8,6 +8,12 @@ package pull | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"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" | 	"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("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")) | 	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) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -329,7 +329,7 @@ | |||||||
| 									{{.CsrfTokenHtml}} | 									{{.CsrfTokenHtml}} | ||||||
| 									<input type="hidden" name="head_commit_id" value="{{.PullHeadCommitID}}"> | 									<input type="hidden" name="head_commit_id" value="{{.PullHeadCommitID}}"> | ||||||
| 									<div class="field"> | 									<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> | ||||||
| 									<div class="field"> | 									<div class="field"> | ||||||
| 										<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">Reviewed-on: {{$.Issue.HTMLURL}}
{{$approvers}}</textarea> | 										<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">Reviewed-on: {{$.Issue.HTMLURL}}
{{$approvers}}</textarea> | ||||||
| @@ -375,10 +375,7 @@ | |||||||
| 									{{.CsrfTokenHtml}} | 									{{.CsrfTokenHtml}} | ||||||
| 									<input type="hidden" name="head_commit_id" value="{{.PullHeadCommitID}}"> | 									<input type="hidden" name="head_commit_id" value="{{.PullHeadCommitID}}"> | ||||||
| 									<div class="field"> | 									<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}}
{{$approvers}}</textarea> |  | ||||||
| 									</div> | 									</div> | ||||||
| 									<button class="ui green button" type="submit" name="do" value="rebase-merge"> | 									<button class="ui green button" type="submit" name="do" value="rebase-merge"> | ||||||
| 										{{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}} | 										{{$.i18n.Tr "repo.pulls.rebase_merge_commit_pull_request"}} | ||||||
| @@ -401,7 +398,7 @@ | |||||||
| 									{{.CsrfTokenHtml}} | 									{{.CsrfTokenHtml}} | ||||||
| 									<input type="hidden" name="head_commit_id" value="{{.PullHeadCommitID}}"> | 									<input type="hidden" name="head_commit_id" value="{{.PullHeadCommitID}}"> | ||||||
| 									<div class="field"> | 									<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> | ||||||
| 									<div class="field"> | 									<div class="field"> | ||||||
| 										<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">{{.GetCommitMessages}}Reviewed-on: {{$.Issue.HTMLURL}}
{{$approvers}}</textarea> | 										<textarea name="merge_message_field" rows="5" placeholder="{{$.i18n.Tr "repo.editor.commit_message_desc"}}">{{.GetCommitMessages}}Reviewed-on: {{$.Issue.HTMLURL}}
{{$approvers}}</textarea> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user