Skip to content

Commit 32b5ea1

Browse files
committed
Prevent merge messages from being sorted to the top of email chains (go-gitea#18566)
Backport go-gitea#18566 Gitea will currrently resend the same message-id for the closed/merged/reopened messages for issues. This will cause the merged message to leap to the top of an email chain and become out of sync. This PR adds specific suffices for these actions. Fix go-gitea#18560 Signed-off-by: Andrew Thornton <[email protected]>
1 parent 70ffec4 commit 32b5ea1

File tree

2 files changed

+131
-4
lines changed

2 files changed

+131
-4
lines changed

services/mailer/mail.go

+17-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"strconv"
1515
"strings"
1616
texttmpl "text/template"
17+
"time"
1718

1819
"code.gitea.io/gitea/models"
1920
repo_model "code.gitea.io/gitea/models/repo"
@@ -297,13 +298,15 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
297298
}
298299

299300
// Make sure to compose independent messages to avoid leaking user emails
301+
msgID := createReference(ctx.Issue, ctx.Comment, ctx.ActionType)
302+
reference := createReference(ctx.Issue, nil, models.ActionType(0))
303+
300304
msgs := make([]*Message, 0, len(recipients))
301305
for _, recipient := range recipients {
302306
msg := NewMessageFrom([]string{recipient.Email}, ctx.Doer.DisplayName(), setting.MailService.FromEmail, subject, mailBody.String())
303307
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)
304308

305-
msg.SetHeader("Message-ID", "<"+createReference(ctx.Issue, ctx.Comment)+">")
306-
reference := createReference(ctx.Issue, nil)
309+
msg.SetHeader("Message-ID", "<"+msgID+">")
307310
msg.SetHeader("In-Reply-To", "<"+reference+">")
308311
msg.SetHeader("References", "<"+reference+">")
309312

@@ -317,7 +320,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
317320
return msgs, nil
318321
}
319322

320-
func createReference(issue *models.Issue, comment *models.Comment) string {
323+
func createReference(issue *models.Issue, comment *models.Comment, actionType models.ActionType) string {
321324
var path string
322325
if issue.IsPull {
323326
path = "pulls"
@@ -328,6 +331,17 @@ func createReference(issue *models.Issue, comment *models.Comment) string {
328331
var extra string
329332
if comment != nil {
330333
extra = fmt.Sprintf("/comment/%d", comment.ID)
334+
} else {
335+
switch actionType {
336+
case models.ActionCloseIssue, models.ActionClosePullRequest:
337+
extra = fmt.Sprintf("/close/%d", time.Now().UnixNano()/1e6)
338+
case models.ActionReopenIssue, models.ActionReopenPullRequest:
339+
extra = fmt.Sprintf("/reopen/%d", time.Now().UnixNano()/1e6)
340+
case models.ActionMergePullRequest:
341+
extra = fmt.Sprintf("/merge/%d", time.Now().UnixNano()/1e6)
342+
case models.ActionPullRequestReadyForReview:
343+
extra = fmt.Sprintf("/ready/%d", time.Now().UnixNano()/1e6)
344+
}
331345
}
332346

333347
return fmt.Sprintf("%s/%s/%d%s@%s", issue.Repo.FullName(), path, issue.Index, extra, setting.Domain)

services/mailer/mail_test.go

+114-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ package mailer
66

77
import (
88
"bytes"
9+
"fmt"
910
"html/template"
11+
"strings"
1012
"testing"
1113
texttmpl "text/template"
1214

@@ -15,7 +17,6 @@ import (
1517
"code.gitea.io/gitea/models/unittest"
1618
user_model "code.gitea.io/gitea/models/user"
1719
"code.gitea.io/gitea/modules/setting"
18-
1920
"github.com/stretchr/testify/assert"
2021
)
2122

@@ -236,3 +237,115 @@ func TestGenerateAdditionalHeaders(t *testing.T) {
236237
}
237238
}
238239
}
240+
241+
func Test_createReference(t *testing.T) {
242+
_, _, issue, comment := prepareMailerTest(t)
243+
_, _, pullIssue, _ := prepareMailerTest(t)
244+
pullIssue.IsPull = true
245+
246+
type args struct {
247+
issue *models.Issue
248+
comment *models.Comment
249+
actionType models.ActionType
250+
}
251+
tests := []struct {
252+
name string
253+
args args
254+
prefix string
255+
suffix string
256+
}{
257+
{
258+
name: "Open Issue",
259+
args: args{
260+
issue: issue,
261+
actionType: models.ActionCreateIssue,
262+
},
263+
prefix: fmt.Sprintf("%s/issues/%d@%s", issue.Repo.FullName(), issue.Index, setting.Domain),
264+
},
265+
{
266+
name: "Open Pull",
267+
args: args{
268+
issue: pullIssue,
269+
actionType: models.ActionCreatePullRequest,
270+
},
271+
prefix: fmt.Sprintf("%s/pulls/%d@%s", issue.Repo.FullName(), issue.Index, setting.Domain),
272+
},
273+
{
274+
name: "Comment Issue",
275+
args: args{
276+
issue: issue,
277+
comment: comment,
278+
actionType: models.ActionCommentIssue,
279+
},
280+
prefix: fmt.Sprintf("%s/issues/%d/comment/%d@%s", issue.Repo.FullName(), issue.Index, comment.ID, setting.Domain),
281+
},
282+
{
283+
name: "Comment Pull",
284+
args: args{
285+
issue: pullIssue,
286+
comment: comment,
287+
actionType: models.ActionCommentPull,
288+
},
289+
prefix: fmt.Sprintf("%s/pulls/%d/comment/%d@%s", issue.Repo.FullName(), issue.Index, comment.ID, setting.Domain),
290+
},
291+
{
292+
name: "Close Issue",
293+
args: args{
294+
issue: issue,
295+
actionType: models.ActionCloseIssue,
296+
},
297+
prefix: fmt.Sprintf("%s/issues/%d/close/", issue.Repo.FullName(), issue.Index),
298+
},
299+
{
300+
name: "Close Pull",
301+
args: args{
302+
issue: pullIssue,
303+
actionType: models.ActionClosePullRequest,
304+
},
305+
prefix: fmt.Sprintf("%s/pulls/%d/close/", issue.Repo.FullName(), issue.Index),
306+
},
307+
{
308+
name: "Reopen Issue",
309+
args: args{
310+
issue: issue,
311+
actionType: models.ActionReopenIssue,
312+
},
313+
prefix: fmt.Sprintf("%s/issues/%d/reopen/", issue.Repo.FullName(), issue.Index),
314+
},
315+
{
316+
name: "Reopen Pull",
317+
args: args{
318+
issue: pullIssue,
319+
actionType: models.ActionReopenPullRequest,
320+
},
321+
prefix: fmt.Sprintf("%s/pulls/%d/reopen/", issue.Repo.FullName(), issue.Index),
322+
},
323+
{
324+
name: "Merge Pull",
325+
args: args{
326+
issue: pullIssue,
327+
actionType: models.ActionMergePullRequest,
328+
},
329+
prefix: fmt.Sprintf("%s/pulls/%d/merge/", issue.Repo.FullName(), issue.Index),
330+
},
331+
{
332+
name: "Ready Pull",
333+
args: args{
334+
issue: pullIssue,
335+
actionType: models.ActionPullRequestReadyForReview,
336+
},
337+
prefix: fmt.Sprintf("%s/pulls/%d/ready/", issue.Repo.FullName(), issue.Index),
338+
},
339+
}
340+
for _, tt := range tests {
341+
t.Run(tt.name, func(t *testing.T) {
342+
got := createReference(tt.args.issue, tt.args.comment, tt.args.actionType)
343+
if !strings.HasPrefix(got, tt.prefix) {
344+
t.Errorf("createReference() = %v, want %v", got, tt.prefix)
345+
}
346+
if !strings.HasSuffix(got, tt.suffix) {
347+
t.Errorf("createReference() = %v, want %v", got, tt.prefix)
348+
}
349+
})
350+
}
351+
}

0 commit comments

Comments
 (0)