diff --git a/models/repo/git.go b/models/repo/git.go index 692176c8f6..11f6452be5 100644 --- a/models/repo/git.go +++ b/models/repo/git.go @@ -29,6 +29,8 @@ const ( MergeStyleRebaseUpdate MergeStyle = "rebase-update-only" ) +var MergeStyles = []MergeStyle{MergeStyleMerge, MergeStyleRebase, MergeStyleRebaseMerge, MergeStyleSquash, MergeStyleFastForwardOnly, MergeStyleManuallyMerged, MergeStyleRebaseUpdate} + type UpdateStyle string const ( diff --git a/modules/git/tests/repos/templates_repo/COMMIT_EDITMSG b/modules/git/tests/repos/templates_repo/COMMIT_EDITMSG new file mode 100644 index 0000000000..a77fa514de --- /dev/null +++ b/modules/git/tests/repos/templates_repo/COMMIT_EDITMSG @@ -0,0 +1 @@ +Initial diff --git a/modules/git/tests/repos/templates_repo/HEAD b/modules/git/tests/repos/templates_repo/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/modules/git/tests/repos/templates_repo/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/modules/git/tests/repos/templates_repo/config b/modules/git/tests/repos/templates_repo/config new file mode 100644 index 0000000000..a4ef456cbc --- /dev/null +++ b/modules/git/tests/repos/templates_repo/config @@ -0,0 +1,5 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + logallrefupdates = true diff --git a/modules/git/tests/repos/templates_repo/description b/modules/git/tests/repos/templates_repo/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/modules/git/tests/repos/templates_repo/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/modules/git/tests/repos/templates_repo/index b/modules/git/tests/repos/templates_repo/index new file mode 100644 index 0000000000..2b3f95a155 Binary files /dev/null and b/modules/git/tests/repos/templates_repo/index differ diff --git a/modules/git/tests/repos/templates_repo/info/exclude b/modules/git/tests/repos/templates_repo/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/modules/git/tests/repos/templates_repo/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/modules/git/tests/repos/templates_repo/info/refs b/modules/git/tests/repos/templates_repo/info/refs new file mode 100644 index 0000000000..8ee4ab3ff8 --- /dev/null +++ b/modules/git/tests/repos/templates_repo/info/refs @@ -0,0 +1 @@ +45697427ce0595075c5c8efa42567f050208510d refs/heads/master diff --git a/modules/git/tests/repos/templates_repo/objects/info/commit-graph b/modules/git/tests/repos/templates_repo/objects/info/commit-graph new file mode 100644 index 0000000000..8904092586 Binary files /dev/null and b/modules/git/tests/repos/templates_repo/objects/info/commit-graph differ diff --git a/modules/git/tests/repos/templates_repo/objects/info/packs b/modules/git/tests/repos/templates_repo/objects/info/packs new file mode 100644 index 0000000000..5833126b59 --- /dev/null +++ b/modules/git/tests/repos/templates_repo/objects/info/packs @@ -0,0 +1,2 @@ +P pack-abb44544ae19d590e95822e963f78d069d27ba9e.pack + diff --git a/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.bitmap b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.bitmap new file mode 100644 index 0000000000..6bd87bf13c Binary files /dev/null and b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.bitmap differ diff --git a/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.idx b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.idx new file mode 100644 index 0000000000..b530533e1b Binary files /dev/null and b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.idx differ diff --git a/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.pack b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.pack new file mode 100644 index 0000000000..6bbcbb6546 Binary files /dev/null and b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.pack differ diff --git a/modules/git/tests/repos/templates_repo/packed-refs b/modules/git/tests/repos/templates_repo/packed-refs new file mode 100644 index 0000000000..eb6e7a8add --- /dev/null +++ b/modules/git/tests/repos/templates_repo/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +45697427ce0595075c5c8efa42567f050208510d refs/heads/master diff --git a/modules/git/tests/repos/templates_repo/refs/heads/.gitkeep b/modules/git/tests/repos/templates_repo/refs/heads/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/git/tests/repos/templates_repo/refs/tags/.gitkeep b/modules/git/tests/repos/templates_repo/refs/tags/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/pull/check.go b/services/pull/check.go index 6002e2ae26..c31d107605 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -404,6 +404,10 @@ func CheckPRsForBaseBranch(ctx context.Context, baseRepo *repo_model.Repository, // Init runs the task queue to test all the checking status pull requests func Init() error { + if err := LoadMergeMessageTemplates(); err != nil { + return err + } + prPatchCheckerQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_patch_checker", handler) if prPatchCheckerQueue == nil { diff --git a/services/pull/merge.go b/services/pull/merge.go index f69f8a87b4..55ce869497 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -35,6 +35,30 @@ import ( notify_service "forgejo.org/services/notify" ) +var mergeMessageTemplates = make(map[repo_model.MergeStyle]string, len(repo_model.MergeStyles)) + +func LoadMergeMessageTemplates() error { + // Load templates for all known merge styles + for _, mergeStyle := range repo_model.MergeStyles { + templateFilename := filepath.Join( + setting.CustomPath, + "default_merge_message", + fmt.Sprintf("%s_TEMPLATE.md", strings.ToUpper(string(mergeStyle))), + ) + + content, err := os.ReadFile(templateFilename) + if err == nil { + mergeMessageTemplates[mergeStyle] = string(content) + } else if os.IsNotExist(err) { + // The file no longer exists, so delete any previous content + delete(mergeMessageTemplates, mergeStyle) + } else { + return err + } + } + return nil +} + // getMergeMessage composes the message used when merging a pull request. func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issues_model.PullRequest, mergeStyle repo_model.MergeStyle, extraVars map[string]string) (message, body string, err error) { if err := pr.LoadBaseRepo(ctx); err != nil { @@ -79,6 +103,13 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue if _, ok := err.(git.ErrNotExist); ok { templateContent, err = commit.GetFileContent(templateFilepathGitea, setting.Repository.PullRequest.DefaultMergeMessageSize) } + + if _, ok := err.(git.ErrNotExist); ok { + if preloadedContent, ok := mergeMessageTemplates[mergeStyle]; ok { + templateContent, err = preloadedContent, nil + } + } + if err != nil { if !git.IsErrNotExist(err) { return "", "", err diff --git a/services/pull/merge_test.go b/services/pull/merge_test.go index 2a26759956..3684c2120a 100644 --- a/services/pull/merge_test.go +++ b/services/pull/merge_test.go @@ -4,9 +4,17 @@ package pull import ( + "os" + "path" + "strings" "testing" + repo_model "forgejo.org/models/repo" + "forgejo.org/modules/setting" + "forgejo.org/modules/test" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_expandDefaultMergeMessage(t *testing.T) { @@ -90,3 +98,51 @@ func TestAddCommitMessageTailer(t *testing.T) { assert.Equal(t, "title\n\nTest-tailer: v1\nTest-tailer: v2", AddCommitMessageTrailer("title\n\nTest-tailer: v1", "Test-tailer", "v2")) assert.Equal(t, "title\n\nTest-tailer: v1\nTest-tailer: v2", AddCommitMessageTrailer("title\n\nTest-tailer: v1\n", "Test-tailer", "v2")) } + +func prepareLoadMergeMessageTemplates(targetDir string) error { + for _, template := range []string{"MERGE", "REBASE", "REBASE-MERGE", "SQUASH", "MANUALLY-MERGED", "REBASE-UPDATE-ONLY"} { + file, err := os.Create(path.Join(targetDir, template+"_TEMPLATE.md")) + defer file.Close() + + if err == nil { + _, err = file.WriteString("Contents for " + template) + } + + if err != nil { + return err + } + } + + return nil +} + +func TestLoadMergeMessageTemplates(t *testing.T) { + defer test.MockVariableValue(&setting.CustomPath, t.TempDir())() + templateTemp := path.Join(setting.CustomPath, "default_merge_message") + + require.NoError(t, os.MkdirAll(templateTemp, 0o755)) + require.NoError(t, prepareLoadMergeMessageTemplates(templateTemp)) + + testStyles := []repo_model.MergeStyle{ + repo_model.MergeStyleMerge, + repo_model.MergeStyleRebase, + repo_model.MergeStyleRebaseMerge, + repo_model.MergeStyleSquash, + repo_model.MergeStyleManuallyMerged, + repo_model.MergeStyleRebaseUpdate, + } + + // Load all templates + require.NoError(t, LoadMergeMessageTemplates()) + + // Check their correctness + assert.Len(t, mergeMessageTemplates, len(testStyles)) + for _, mergeStyle := range testStyles { + assert.Equal(t, "Contents for "+strings.ToUpper(string(mergeStyle)), mergeMessageTemplates[mergeStyle]) + } + + // Unload all templates + require.NoError(t, os.RemoveAll(templateTemp)) + require.NoError(t, LoadMergeMessageTemplates()) + assert.Empty(t, mergeMessageTemplates) +} diff --git a/services/pull/pull_test.go b/services/pull/pull_test.go index 010b7a6404..99607c5b35 100644 --- a/services/pull/pull_test.go +++ b/services/pull/pull_test.go @@ -92,3 +92,39 @@ func TestPullRequest_GetDefaultMergeMessage_ExternalTracker(t *testing.T) { assert.Equal(t, "Merge pull request 'issue3' (#3) from user2/repo2:branch2 into master", mergeMessage) } + +func TestPullRequest_GetDefaultMergeMessage_GlobalTemplate(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) + + require.NoError(t, pr.LoadBaseRepo(t.Context())) + gitRepo, err := gitrepo.OpenRepository(t.Context(), pr.BaseRepo) + require.NoError(t, err) + defer gitRepo.Close() + + templateRepo, err := git.OpenRepository(t.Context(), "./../../modules/git/tests/repos/templates_repo") + require.NoError(t, err) + defer templateRepo.Close() + + mergeMessageTemplates[repo_model.MergeStyleMerge] = "${PullRequestTitle} (${PullRequestReference})\n${PullRequestDescription}" + + // Check template is used for Merge... + mergeMessage, body, err := GetDefaultMergeMessage(t.Context(), gitRepo, pr, repo_model.MergeStyleMerge) + require.NoError(t, err) + + assert.Equal(t, "issue3 (#3)", mergeMessage) + assert.Equal(t, "content for the third issue", body) + + // ...but not for RebaseMerge + mergeMessage, _, err = GetDefaultMergeMessage(t.Context(), gitRepo, pr, repo_model.MergeStyleRebaseMerge) + require.NoError(t, err) + + assert.Equal(t, "Merge pull request 'issue3' (#3) from branch2 into master", mergeMessage) + + // ...and that custom Merge template takes priority + mergeMessage, body, err = GetDefaultMergeMessage(t.Context(), templateRepo, pr, repo_model.MergeStyleMerge) + require.NoError(t, err) + + assert.Equal(t, "Default merge message template", mergeMessage) + assert.Equal(t, "This line was read from .forgejo/default_merge_message/MERGE_TEMPLATE.md", body) +}