diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 55de85646c..9d4434f7b0 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -6,6 +6,8 @@
package templates
import (
+ "bytes"
+ "context"
"fmt"
"html"
"html/template"
@@ -29,6 +31,23 @@ func NewFuncMap() template.FuncMap {
return map[string]any{
"ctx": func() any { return nil }, // template context function
+ "ExecuteTemplate": func(ctx context.Context, tmplName string, args any) template.HTML {
+ h := HTMLRenderer()
+ tmpl, err := h.TemplateLookup(tmplName, ctx)
+ if err != nil {
+ panic("Template not found: " + tmplName)
+ }
+
+ buf := bytes.Buffer{}
+ if err := tmpl.Execute(&buf, args); err != nil {
+ panic("Error while executing template")
+ }
+
+ // We can safely return this as `template.HTML` as html/template will
+ // already make sure it's sanitized.
+ return template.HTML(buf.String())
+ },
+
"DumpVar": dumpVar,
// -----------------------------------------------------------------
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 856a835b32..609e8c75b2 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1746,8 +1746,8 @@ issues.add_time_sum_to_small = No time was entered.
issues.time_spent_total = Total time spent
issues.time_spent_from_all_authors = `Total time spent: %s`
issues.due_date = Due date
-issues.push_commit_1 = added %d commit %s
-issues.push_commits_n = added %d commits %s
+discussion.added_commits_one = %[1]s added %[2]d commit %[3]s
+discussion.added_commits_few = %[1]s added %[2]d commits %[3]s
issues.force_push_codes = `force-pushed %[1]s from %[2]s
to %[4]s
%[6]s`
issues.force_push_compare = Compare
issues.due_date_form = yyyy-mm-dd
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index c8eabb9345..1d45b79d0d 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -549,11 +549,10 @@
{{svg "octicon-repo-push"}}
- {{template "shared/user/authorlink" .Poster}}
{{if .IsForcePush}}
- {{ctx.Locale.Tr "repo.issues.force_push_codes" $.Issue.PullRequest.HeadBranch (ShortSha .OldCommit) ($.Issue.Repo.CommitLink .OldCommit) (ShortSha .NewCommit) ($.Issue.Repo.CommitLink .NewCommit) $createdStr "ui sha"}}
+ {{template "shared/user/authorlink" .Poster}} {{ctx.Locale.Tr "repo.issues.force_push_codes" $.Issue.PullRequest.HeadBranch (ShortSha .OldCommit) ($.Issue.Repo.CommitLink .OldCommit) (ShortSha .NewCommit) ($.Issue.Repo.CommitLink .NewCommit) $createdStr "ui sha"}}
{{else}}
- {{ctx.Locale.TrN (len .Commits) "repo.issues.push_commit_1" "repo.issues.push_commits_n" (len .Commits) $createdStr}}
+ {{ctx.Locale.TrN (len .Commits) "repo.discussion.added_commits_one" "repo.discussion.added_commits_few" (ExecuteTemplate ctx "shared/user/authorlink" .Poster) (len .Commits) $createdStr}}
{{end}}
{{if and .IsForcePush $.Issue.PullRequest.BaseRepo.Name}}
diff --git a/tests/integration/discussion_events_test.go b/tests/integration/discussion_events_test.go
new file mode 100644
index 0000000000..50c58dd5c5
--- /dev/null
+++ b/tests/integration/discussion_events_test.go
@@ -0,0 +1,69 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package integration
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "path"
+ "regexp"
+ "strings"
+ "testing"
+
+ auth_model "code.gitea.io/gitea/models/auth"
+ api "code.gitea.io/gitea/modules/structs"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// TestDiscussionEvents is a test for various events displayed in the timelines of pulls and issues
+func TestDiscussionEvents(t *testing.T) {
+ repoName := "discussion-timeline-tests"
+ user2 := "user2"
+ description := "This PR will be used for testing events in discussions"
+ // Expected branch name when initializing repo automatically
+ defaultBranch := "master"
+ htmlCleaner := regexp.MustCompile(`[\t\n]`)
+
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ sessionUser2 := loginUser(t, user2)
+ tokenUser2 := getTokenForLoggedInUser(t, sessionUser2, auth_model.AccessTokenScopeAll)
+
+ // Create test repo
+ var repo api.Repository
+ resp := sessionUser2.MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
+ Name: repoName,
+ AutoInit: true,
+ }).AddTokenAuth(tokenUser2), http.StatusCreated)
+ DecodeJSON(t, resp, &repo)
+
+ // == Test pulls ==
+
+ // Open a new PR as user2
+ testEditFileToNewBranch(t, sessionUser2, user2, repo.Name, defaultBranch, "comment-labels", "README.md", description)
+ sessionUser2.MakeRequest(t, NewRequestWithValues(t, "POST", path.Join(repo.FullName, "compare", fmt.Sprintf("%s...comment-labels", defaultBranch)),
+ map[string]string{
+ "_csrf": GetCSRF(t, sessionUser2, path.Join(repo.FullName, "compare", fmt.Sprintf("%s...comment-labels", defaultBranch))),
+ "title": description,
+ },
+ ), http.StatusOK)
+
+ // Pull number, expected to be 1 in a fresh repo
+ testPullID := "1"
+
+ // Get the PR page and find all events
+ response := sessionUser2.MakeRequest(t, NewRequest(t, "GET", path.Join(repo.FullName, "pulls", testPullID)), http.StatusOK)
+ page := NewHTMLParser(t, response.Body)
+ events := page.Find(".timeline .timeline-item.event .text")
+
+ // Check the event. Should contain: " added 1 commit "
+ event := events.Eq(0)
+ eventHTML, _ := event.Html()
+ eventText := htmlCleaner.ReplaceAllString(strings.TrimSpace(event.Text()), "")
+ assert.Contains(t, eventHTML, `href="/user2">user2`)
+ assert.Contains(t, eventHTML, `