Skip to content

Commit 2d3ebe8

Browse files
authored
Merge message template support for rebase without merge commit (#22669)
Use `default_merge_message/REBASE_TEMPLATE.md` for amending the message of the last commit in the list of commits that was merged. Previously this template was mentioned in the documentation but not actually used. In this template additional variables `CommitTitle` and `CommitBody` are available, for the title and body of the commit. Ideally the message of every commit would be updated using the template, but doing an interactive rebase or merging commits one by one is complicated, so that is left as a future improvement.
1 parent 84d93c8 commit 2d3ebe8

File tree

3 files changed

+106
-21
lines changed

3 files changed

+106
-21
lines changed

docs/content/doc/usage/merge-message-templates.en-us.md

+7
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,10 @@ You can use the following variables enclosed in `${}` inside these templates whi
4848
- PullRequestIndex: Pull request's index number
4949
- PullRequestReference: Pull request's reference char with index number. i.e. #1, !2
5050
- ClosingIssues: return a string contains all issues which will be closed by this pull request i.e. `close #1, close #2`
51+
52+
## Rebase
53+
54+
When rebasing without a merge commit, `REBASE_TEMPLATE.md` modifies the message of the last commit. The following additional variables are available in this template:
55+
56+
- CommitTitle: Commit's title
57+
- CommitBody: Commits's body text

services/pull/merge.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ import (
3434
issue_service "code.gitea.io/gitea/services/issue"
3535
)
3636

37-
// GetDefaultMergeMessage returns default message used when merging pull request
38-
func GetDefaultMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issues_model.PullRequest, mergeStyle repo_model.MergeStyle) (message, body string, err error) {
37+
// getMergeMessage composes the message used when merging a pull request.
38+
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) {
3939
if err := pr.LoadBaseRepo(ctx); err != nil {
4040
return "", "", err
4141
}
@@ -81,6 +81,9 @@ func GetDefaultMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr
8181
vars["HeadRepoOwnerName"] = pr.HeadRepo.OwnerName
8282
vars["HeadRepoName"] = pr.HeadRepo.Name
8383
}
84+
for extraKey, extraValue := range extraVars {
85+
vars[extraKey] = extraValue
86+
}
8487
refs, err := pr.ResolveCrossReferences(ctx)
8588
if err == nil {
8689
closeIssueIndexes := make([]string, 0, len(refs))
@@ -133,6 +136,11 @@ func expandDefaultMergeMessage(template string, vars map[string]string) (message
133136
return os.Expand(message, mapping), os.Expand(body, mapping)
134137
}
135138

139+
// GetDefaultMergeMessage returns default message used when merging pull request
140+
func GetDefaultMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issues_model.PullRequest, mergeStyle repo_model.MergeStyle) (message, body string, err error) {
141+
return getMergeMessage(ctx, baseGitRepo, pr, mergeStyle, nil)
142+
}
143+
136144
// Merge merges pull request to base repository.
137145
// Caller should check PR is ready to be merged (review and status checks)
138146
func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, wasAutoMerged bool) error {

services/pull/merge_rebase.go

+89-19
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,99 @@ package pull
55

66
import (
77
"fmt"
8+
"strings"
89

910
repo_model "code.gitea.io/gitea/models/repo"
1011
"code.gitea.io/gitea/modules/git"
1112
"code.gitea.io/gitea/modules/log"
1213
)
1314

14-
// doMergeStyleRebase rebaases the tracking branch on the base branch as the current HEAD with or with a merge commit to the original pr branch
15+
// getRebaseAmendMessage composes the message to amend commits in rebase merge of a pull request.
16+
func getRebaseAmendMessage(ctx *mergeContext, baseGitRepo *git.Repository) (message string, err error) {
17+
// Get existing commit message.
18+
commitMessage, _, err := git.NewCommand(ctx, "show", "--format=%B", "-s").RunStdString(&git.RunOpts{Dir: ctx.tmpBasePath})
19+
if err != nil {
20+
return "", err
21+
}
22+
23+
commitTitle, commitBody, _ := strings.Cut(commitMessage, "\n")
24+
extraVars := map[string]string{"CommitTitle": strings.TrimSpace(commitTitle), "CommitBody": strings.TrimSpace(commitBody)}
25+
26+
message, body, err := getMergeMessage(ctx, baseGitRepo, ctx.pr, repo_model.MergeStyleRebase, extraVars)
27+
if err != nil || message == "" {
28+
return "", err
29+
}
30+
31+
if len(body) > 0 {
32+
message = message + "\n\n" + body
33+
}
34+
return message, err
35+
}
36+
37+
// Perform rebase merge without merge commit.
38+
func doMergeRebaseFastForward(ctx *mergeContext) error {
39+
baseHeadSHA, err := git.GetFullCommitID(ctx, ctx.tmpBasePath, "HEAD")
40+
if err != nil {
41+
return fmt.Errorf("Failed to get full commit id for HEAD: %w", err)
42+
}
43+
44+
cmd := git.NewCommand(ctx, "merge", "--ff-only").AddDynamicArguments(stagingBranch)
45+
if err := runMergeCommand(ctx, repo_model.MergeStyleRebase, cmd); err != nil {
46+
log.Error("Unable to merge staging into base: %v", err)
47+
return err
48+
}
49+
50+
// Check if anything actually changed before we amend the message, fast forward can skip commits.
51+
newMergeHeadSHA, err := git.GetFullCommitID(ctx, ctx.tmpBasePath, "HEAD")
52+
if err != nil {
53+
return fmt.Errorf("Failed to get full commit id for HEAD: %w", err)
54+
}
55+
if baseHeadSHA == newMergeHeadSHA {
56+
return nil
57+
}
58+
59+
// Original repo to read template from.
60+
baseGitRepo, err := git.OpenRepository(ctx, ctx.pr.BaseRepo.RepoPath())
61+
if err != nil {
62+
log.Error("Unable to get Git repo for rebase: %v", err)
63+
return err
64+
}
65+
defer baseGitRepo.Close()
66+
67+
// Amend last commit message based on template, if one exists
68+
newMessage, err := getRebaseAmendMessage(ctx, baseGitRepo)
69+
if err != nil {
70+
log.Error("Unable to get commit message for amend: %v", err)
71+
return err
72+
}
73+
74+
if newMessage != "" {
75+
if err := git.NewCommand(ctx, "commit", "--amend").AddOptionFormat("--message=%s", newMessage).Run(&git.RunOpts{Dir: ctx.tmpBasePath}); err != nil {
76+
log.Error("Unable to amend commit message: %v", err)
77+
return err
78+
}
79+
}
80+
81+
return nil
82+
}
83+
84+
// Perform rebase merge with merge commit.
85+
func doMergeRebaseMergeCommit(ctx *mergeContext, message string) error {
86+
cmd := git.NewCommand(ctx, "merge").AddArguments("--no-ff", "--no-commit").AddDynamicArguments(stagingBranch)
87+
88+
if err := runMergeCommand(ctx, repo_model.MergeStyleRebaseMerge, cmd); err != nil {
89+
log.Error("Unable to merge staging into base: %v", err)
90+
return err
91+
}
92+
if err := commitAndSignNoAuthor(ctx, message); err != nil {
93+
log.Error("Unable to make final commit: %v", err)
94+
return err
95+
}
96+
97+
return nil
98+
}
99+
100+
// doMergeStyleRebase rebases the tracking branch on the base branch as the current HEAD with or with a merge commit to the original pr branch
15101
func doMergeStyleRebase(ctx *mergeContext, mergeStyle repo_model.MergeStyle, message string) error {
16102
if err := rebaseTrackingOnToBase(ctx, mergeStyle); err != nil {
17103
return err
@@ -26,25 +112,9 @@ func doMergeStyleRebase(ctx *mergeContext, mergeStyle repo_model.MergeStyle, mes
26112
ctx.outbuf.Reset()
27113
ctx.errbuf.Reset()
28114

29-
cmd := git.NewCommand(ctx, "merge")
30115
if mergeStyle == repo_model.MergeStyleRebase {
31-
cmd.AddArguments("--ff-only")
32-
} else {
33-
cmd.AddArguments("--no-ff", "--no-commit")
34-
}
35-
cmd.AddDynamicArguments(stagingBranch)
36-
37-
// Prepare merge with commit
38-
if err := runMergeCommand(ctx, mergeStyle, cmd); err != nil {
39-
log.Error("Unable to merge staging into base: %v", err)
40-
return err
41-
}
42-
if mergeStyle == repo_model.MergeStyleRebaseMerge {
43-
if err := commitAndSignNoAuthor(ctx, message); err != nil {
44-
log.Error("Unable to make final commit: %v", err)
45-
return err
46-
}
116+
return doMergeRebaseFastForward(ctx)
47117
}
48118

49-
return nil
119+
return doMergeRebaseMergeCommit(ctx, message)
50120
}

0 commit comments

Comments
 (0)