diff --git a/models/issues/comment.go b/models/issues/comment.go
index d53e5f5949..90c7122174 100644
--- a/models/issues/comment.go
+++ b/models/issues/comment.go
@@ -222,43 +222,51 @@ func (r RoleInRepo) LocaleHelper(lang translation.Locale) string {
 	return lang.TrString("repo.issues.role." + string(r) + "_helper")
 }
 
+type RequestReviewTarget interface {
+	ID() int64
+	Name() string
+	Type() string
+}
+
 // Comment represents a comment in commit and issue page.
 type Comment struct {
-	ID               int64            `xorm:"pk autoincr"`
-	Type             CommentType      `xorm:"INDEX"`
-	PosterID         int64            `xorm:"INDEX"`
-	Poster           *user_model.User `xorm:"-"`
-	OriginalAuthor   string
-	OriginalAuthorID int64
-	IssueID          int64  `xorm:"INDEX"`
-	Issue            *Issue `xorm:"-"`
-	LabelID          int64
-	Label            *Label   `xorm:"-"`
-	AddedLabels      []*Label `xorm:"-"`
-	RemovedLabels    []*Label `xorm:"-"`
-	OldProjectID     int64
-	ProjectID        int64
-	OldProject       *project_model.Project `xorm:"-"`
-	Project          *project_model.Project `xorm:"-"`
-	OldMilestoneID   int64
-	MilestoneID      int64
-	OldMilestone     *Milestone `xorm:"-"`
-	Milestone        *Milestone `xorm:"-"`
-	TimeID           int64
-	Time             *TrackedTime `xorm:"-"`
-	AssigneeID       int64
-	RemovedAssignee  bool
-	Assignee         *user_model.User   `xorm:"-"`
-	AssigneeTeamID   int64              `xorm:"NOT NULL DEFAULT 0"`
-	AssigneeTeam     *organization.Team `xorm:"-"`
-	ResolveDoerID    int64
-	ResolveDoer      *user_model.User `xorm:"-"`
-	OldTitle         string
-	NewTitle         string
-	OldRef           string
-	NewRef           string
-	DependentIssueID int64  `xorm:"index"` // This is used by issue_service.deleteIssue
-	DependentIssue   *Issue `xorm:"-"`
+	ID                   int64            `xorm:"pk autoincr"`
+	Type                 CommentType      `xorm:"INDEX"`
+	PosterID             int64            `xorm:"INDEX"`
+	Poster               *user_model.User `xorm:"-"`
+	OriginalAuthor       string
+	OriginalAuthorID     int64
+	IssueID              int64  `xorm:"INDEX"`
+	Issue                *Issue `xorm:"-"`
+	LabelID              int64
+	Label                *Label                `xorm:"-"`
+	AddedLabels          []*Label              `xorm:"-"`
+	RemovedLabels        []*Label              `xorm:"-"`
+	AddedRequestReview   []RequestReviewTarget `xorm:"-"`
+	RemovedRequestReview []RequestReviewTarget `xorm:"-"`
+	OldProjectID         int64
+	ProjectID            int64
+	OldProject           *project_model.Project `xorm:"-"`
+	Project              *project_model.Project `xorm:"-"`
+	OldMilestoneID       int64
+	MilestoneID          int64
+	OldMilestone         *Milestone `xorm:"-"`
+	Milestone            *Milestone `xorm:"-"`
+	TimeID               int64
+	Time                 *TrackedTime `xorm:"-"`
+	AssigneeID           int64
+	RemovedAssignee      bool
+	Assignee             *user_model.User   `xorm:"-"`
+	AssigneeTeamID       int64              `xorm:"NOT NULL DEFAULT 0"`
+	AssigneeTeam         *organization.Team `xorm:"-"`
+	ResolveDoerID        int64
+	ResolveDoer          *user_model.User `xorm:"-"`
+	OldTitle             string
+	NewTitle             string
+	OldRef               string
+	NewRef               string
+	DependentIssueID     int64  `xorm:"index"` // This is used by issue_service.deleteIssue
+	DependentIssue       *Issue `xorm:"-"`
 
 	CommitID        int64
 	Line            int64 // - previous line / + proposed line
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index aeae8204ad..5b5470b87e 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -182,6 +182,7 @@ func NewFuncMap() template.FuncMap {
 		"RenderMarkdownToHtml": RenderMarkdownToHtml,
 		"RenderLabel":          RenderLabel,
 		"RenderLabels":         RenderLabels,
+		"RenderReviewRequest":  RenderReviewRequest,
 
 		// -----------------------------------------------------------------
 		// misc
diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go
index c53bdd876f..3c337ae895 100644
--- a/modules/templates/util_render.go
+++ b/modules/templates/util_render.go
@@ -262,3 +262,15 @@ func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issu
 	htmlCode += "</span>"
 	return template.HTML(htmlCode)
 }
+
+func RenderReviewRequest(users []issues_model.RequestReviewTarget) template.HTML {
+	usernames := make([]string, 0, len(users))
+	for _, user := range users {
+		usernames = append(usernames, template.HTMLEscapeString(user.Name()))
+	}
+
+	htmlCode := `<span class="review-request-list">`
+	htmlCode += strings.Join(usernames, ", ")
+	htmlCode += "</span>"
+	return template.HTML(htmlCode)
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index b87fd2a69b..cdfd409d96 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1791,9 +1791,12 @@ issues.review.left_comment = left a comment
 issues.review.content.empty = You need to leave a comment indicating the requested change(s).
 issues.review.reject = requested changes %s
 issues.review.wait = was requested for review %s
-issues.review.add_review_request = requested review from %s %s
-issues.review.remove_review_request = removed review request for %s %s
-issues.review.remove_review_request_self = refused to review %s
+issues.review.add_review_request = requested review from %[1]s %[2]s
+issues.review.add_review_requests = requested reviews from %[1]s %[2]s
+issues.review.remove_review_request = removed review request for %[1]s %[2]s
+issues.review.remove_review_requests = removed review requests for %[1]s %[2]s
+issues.review.remove_review_request_self = refused to review %[1]s %[2]s
+issues.review.add_remove_review_requests = requested reviews from %[1]s and removed review requests for %[2]s %[3]s
 issues.review.pending = Pending
 issues.review.pending.tooltip = This comment is not currently visible to other users. To submit your pending comments, select "%s" -> "%s/%s/%s" at the top of the page.
 issues.review.review = Review
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 2f077572ac..a9351c6a55 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -1825,6 +1825,7 @@ func ViewIssue(ctx *context.Context) {
 
 	// Combine multiple label assignments into a single comment
 	combineLabelComments(issue)
+	combineRequestReviewComments(issue)
 
 	getBranchData(ctx, issue)
 	if issue.IsPull {
@@ -3665,6 +3666,127 @@ func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment,
 	return attachHTML
 }
 
+type RequestReviewTarget struct {
+	user *user_model.User
+	team *organization.Team
+}
+
+func (t *RequestReviewTarget) ID() int64 {
+	if t.user != nil {
+		return t.user.ID
+	}
+	return t.team.ID
+}
+
+func (t *RequestReviewTarget) Name() string {
+	if t.user != nil {
+		return t.user.GetDisplayName()
+	}
+	return t.team.Name
+}
+
+func (t *RequestReviewTarget) Type() string {
+	if t.user != nil {
+		return "user"
+	}
+	return "team"
+}
+
+// combineRequestReviewComments combine the nearby request review comments as one.
+func combineRequestReviewComments(issue *issues_model.Issue) {
+	var prev, cur *issues_model.Comment
+	for i := 0; i < len(issue.Comments); i++ {
+		cur = issue.Comments[i]
+		if i > 0 {
+			prev = issue.Comments[i-1]
+		}
+		if i == 0 || cur.Type != issues_model.CommentTypeReviewRequest ||
+			(prev != nil && prev.PosterID != cur.PosterID) ||
+			(prev != nil && cur.CreatedUnix-prev.CreatedUnix >= 60) {
+			if cur.Type == issues_model.CommentTypeReviewRequest && (cur.Assignee != nil || cur.AssigneeTeam != nil) {
+				if cur.RemovedAssignee {
+					if cur.AssigneeTeam != nil {
+						cur.RemovedRequestReview = append(cur.RemovedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
+					} else {
+						cur.RemovedRequestReview = append(cur.RemovedRequestReview, &RequestReviewTarget{user: cur.Assignee})
+					}
+				} else {
+					if cur.AssigneeTeam != nil {
+						cur.AddedRequestReview = append(cur.AddedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
+					} else {
+						cur.AddedRequestReview = append(cur.AddedRequestReview, &RequestReviewTarget{user: cur.Assignee})
+					}
+				}
+			}
+			continue
+		}
+
+		// Previous comment is not a review request, so cannot group. Start a new group.
+		if prev.Type != issues_model.CommentTypeReviewRequest {
+			if cur.RemovedAssignee {
+				if cur.AssigneeTeam != nil {
+					cur.RemovedRequestReview = append(cur.RemovedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
+				} else {
+					cur.RemovedRequestReview = append(cur.RemovedRequestReview, &RequestReviewTarget{user: cur.Assignee})
+				}
+			} else {
+				if cur.AssigneeTeam != nil {
+					cur.AddedRequestReview = append(cur.AddedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
+				} else {
+					cur.AddedRequestReview = append(cur.AddedRequestReview, &RequestReviewTarget{user: cur.Assignee})
+				}
+			}
+			continue
+		}
+
+		// Start grouping.
+		if cur.RemovedAssignee {
+			addedIndex := slices.IndexFunc(prev.AddedRequestReview, func(t issues_model.RequestReviewTarget) bool {
+				if cur.AssigneeTeam != nil {
+					return cur.AssigneeTeam.ID == t.ID() && t.Type() == "team"
+				}
+				return cur.Assignee.ID == t.ID() && t.Type() == "user"
+			})
+
+			// If for this target a AddedRequestReview, then we remove that entry. If it's not found, then add it to the RemovedRequestReview.
+			if addedIndex == -1 {
+				if cur.AssigneeTeam != nil {
+					prev.RemovedRequestReview = append(prev.RemovedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
+				} else {
+					prev.RemovedRequestReview = append(prev.RemovedRequestReview, &RequestReviewTarget{user: cur.Assignee})
+				}
+			} else {
+				prev.AddedRequestReview = slices.Delete(prev.AddedRequestReview, addedIndex, addedIndex+1)
+			}
+		} else {
+			removedIndex := slices.IndexFunc(prev.RemovedRequestReview, func(t issues_model.RequestReviewTarget) bool {
+				if cur.AssigneeTeam != nil {
+					return cur.AssigneeTeam.ID == t.ID() && t.Type() == "team"
+				}
+				return cur.Assignee.ID == t.ID() && t.Type() == "user"
+			})
+
+			// If for this target a RemovedRequestReview, then we remove that entry. If it's not found, then add it to the AddedRequestReview.
+			if removedIndex == -1 {
+				if cur.AssigneeTeam != nil {
+					prev.AddedRequestReview = append(prev.AddedRequestReview, &RequestReviewTarget{team: cur.AssigneeTeam})
+				} else {
+					prev.AddedRequestReview = append(prev.AddedRequestReview, &RequestReviewTarget{user: cur.Assignee})
+				}
+			} else {
+				prev.RemovedRequestReview = slices.Delete(prev.RemovedRequestReview, removedIndex, removedIndex+1)
+			}
+		}
+
+		// Propoagate creation time.
+		prev.CreatedUnix = cur.CreatedUnix
+
+		// Remove the current comment since it has been combined to prev comment
+		issue.Comments = append(issue.Comments[:i], issue.Comments[i+1:]...)
+		i--
+	}
+}
+
 // combineLabelComments combine the nearby label comments as one.
 func combineLabelComments(issue *issues_model.Issue) {
 	var prev, cur *issues_model.Comment
diff --git a/routers/web/repo/issue_test.go b/routers/web/repo/issue_test.go
index f1d0aac72f..d642c14b5f 100644
--- a/routers/web/repo/issue_test.go
+++ b/routers/web/repo/issue_test.go
@@ -7,6 +7,8 @@ import (
 	"testing"
 
 	issues_model "code.gitea.io/gitea/models/issues"
+	org_model "code.gitea.io/gitea/models/organization"
+	user_model "code.gitea.io/gitea/models/user"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -373,3 +375,432 @@ func TestCombineLabelComments(t *testing.T) {
 		})
 	}
 }
+
+func TestCombineReviewRequests(t *testing.T) {
+	testCases := []struct {
+		name           string
+		beforeCombined []*issues_model.Comment
+		afterCombined  []*issues_model.Comment
+	}{
+		{
+			name: "case 1",
+			beforeCombined: []*issues_model.Comment{
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:            issues_model.CommentTypeReviewRequest,
+					PosterID:        1,
+					RemovedAssignee: true,
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:        issues_model.CommentTypeComment,
+					PosterID:    1,
+					Content:     "test",
+					CreatedUnix: 0,
+				},
+			},
+			afterCombined: []*issues_model.Comment{
+				{
+					Type:               issues_model.CommentTypeReviewRequest,
+					PosterID:           1,
+					CreatedUnix:        0,
+					AddedRequestReview: []issues_model.RequestReviewTarget{},
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+				},
+				{
+					Type:        issues_model.CommentTypeComment,
+					PosterID:    1,
+					Content:     "test",
+					CreatedUnix: 0,
+				},
+			},
+		},
+		{
+			name: "case 2",
+			beforeCombined: []*issues_model.Comment{
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					Assignee: &user_model.User{
+						ID:   2,
+						Name: "Ghost 2",
+					},
+					CreatedUnix: 0,
+				},
+			},
+			afterCombined: []*issues_model.Comment{
+				{
+					Type:        issues_model.CommentTypeReviewRequest,
+					PosterID:    1,
+					CreatedUnix: 0,
+					AddedRequestReview: []issues_model.RequestReviewTarget{
+						&RequestReviewTarget{
+							user: &user_model.User{
+								ID:   1,
+								Name: "Ghost",
+							},
+						},
+						&RequestReviewTarget{
+							user: &user_model.User{
+								ID:   2,
+								Name: "Ghost 2",
+							},
+						},
+					},
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+				},
+			},
+		},
+		{
+			name: "case 3",
+			beforeCombined: []*issues_model.Comment{
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:            issues_model.CommentTypeReviewRequest,
+					PosterID:        1,
+					RemovedAssignee: true,
+					AssigneeTeam: &org_model.Team{
+						ID:   1,
+						Name: "Team 1",
+					},
+					CreatedUnix: 0,
+				},
+			},
+			afterCombined: []*issues_model.Comment{
+				{
+					Type:        issues_model.CommentTypeReviewRequest,
+					PosterID:    1,
+					CreatedUnix: 0,
+					AddedRequestReview: []issues_model.RequestReviewTarget{
+						&RequestReviewTarget{
+							user: &user_model.User{
+								ID:   1,
+								Name: "Ghost",
+							},
+						},
+					},
+					RemovedRequestReview: []issues_model.RequestReviewTarget{
+						&RequestReviewTarget{
+							team: &org_model.Team{
+								ID:   1,
+								Name: "Team 1",
+							},
+						},
+					},
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+				},
+			},
+		},
+		{
+			name: "case 4",
+			beforeCombined: []*issues_model.Comment{
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:            issues_model.CommentTypeReviewRequest,
+					PosterID:        1,
+					RemovedAssignee: true,
+					AssigneeTeam: &org_model.Team{
+						ID:   1,
+						Name: "Team 1",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					AssigneeTeam: &org_model.Team{
+						ID:   1,
+						Name: "Team 1",
+					},
+					CreatedUnix: 0,
+				},
+			},
+			afterCombined: []*issues_model.Comment{
+				{
+					Type:        issues_model.CommentTypeReviewRequest,
+					PosterID:    1,
+					CreatedUnix: 0,
+					AddedRequestReview: []issues_model.RequestReviewTarget{
+						&RequestReviewTarget{
+							user: &user_model.User{
+								ID:   1,
+								Name: "Ghost",
+							},
+						},
+					},
+					RemovedRequestReview: []issues_model.RequestReviewTarget{},
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+				},
+			},
+		},
+		{
+			name: "case 5",
+			beforeCombined: []*issues_model.Comment{
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:            issues_model.CommentTypeReviewRequest,
+					PosterID:        1,
+					RemovedAssignee: true,
+					AssigneeTeam: &org_model.Team{
+						ID:   1,
+						Name: "Team 1",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					AssigneeTeam: &org_model.Team{
+						ID:   1,
+						Name: "Team 1",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:            issues_model.CommentTypeReviewRequest,
+					PosterID:        1,
+					RemovedAssignee: true,
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+					CreatedUnix: 0,
+				},
+			},
+			afterCombined: []*issues_model.Comment{
+				{
+					Type:                 issues_model.CommentTypeReviewRequest,
+					PosterID:             1,
+					CreatedUnix:          0,
+					AddedRequestReview:   []issues_model.RequestReviewTarget{},
+					RemovedRequestReview: []issues_model.RequestReviewTarget{},
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+				},
+			},
+		},
+		{
+			name: "case 6",
+			beforeCombined: []*issues_model.Comment{
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:            issues_model.CommentTypeReviewRequest,
+					PosterID:        1,
+					RemovedAssignee: true,
+					AssigneeTeam: &org_model.Team{
+						ID:   1,
+						Name: "Team 1",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:        issues_model.CommentTypeComment,
+					PosterID:    1,
+					Content:     "test",
+					CreatedUnix: 0,
+				},
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					AssigneeTeam: &org_model.Team{
+						ID:   1,
+						Name: "Team 1",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:            issues_model.CommentTypeReviewRequest,
+					PosterID:        1,
+					RemovedAssignee: true,
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+					CreatedUnix: 0,
+				},
+			},
+			afterCombined: []*issues_model.Comment{
+				{
+					Type:        issues_model.CommentTypeReviewRequest,
+					PosterID:    1,
+					CreatedUnix: 0,
+					RemovedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
+						team: &org_model.Team{
+							ID:   1,
+							Name: "Team 1",
+						},
+					}},
+					AddedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
+						user: &user_model.User{
+							ID:   1,
+							Name: "Ghost",
+						},
+					}},
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+				},
+				{
+					Type:        issues_model.CommentTypeComment,
+					PosterID:    1,
+					Content:     "test",
+					CreatedUnix: 0,
+				},
+				{
+					Type:        issues_model.CommentTypeReviewRequest,
+					PosterID:    1,
+					CreatedUnix: 0,
+					AddedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
+						team: &org_model.Team{
+							ID:   1,
+							Name: "Team 1",
+						},
+					}},
+					RemovedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
+						user: &user_model.User{
+							ID:   1,
+							Name: "Ghost",
+						},
+					}},
+					AssigneeTeam: &org_model.Team{
+						ID:   1,
+						Name: "Team 1",
+					},
+				},
+			},
+		},
+		{
+			name: "case 7",
+			beforeCombined: []*issues_model.Comment{
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+					CreatedUnix: 0,
+				},
+				{
+					Type:     issues_model.CommentTypeReviewRequest,
+					PosterID: 1,
+					AssigneeTeam: &org_model.Team{
+						ID:   1,
+						Name: "Team 1",
+					},
+					CreatedUnix: 61,
+				},
+			},
+			afterCombined: []*issues_model.Comment{
+				{
+					Type:        issues_model.CommentTypeReviewRequest,
+					PosterID:    1,
+					CreatedUnix: 0,
+					AddedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
+						user: &user_model.User{
+							ID:   1,
+							Name: "Ghost",
+						},
+					}},
+					Assignee: &user_model.User{
+						ID:   1,
+						Name: "Ghost",
+					},
+				},
+				{
+					Type:        issues_model.CommentTypeReviewRequest,
+					PosterID:    1,
+					CreatedUnix: 0,
+					RemovedRequestReview: []issues_model.RequestReviewTarget{&RequestReviewTarget{
+						team: &org_model.Team{
+							ID:   1,
+							Name: "Team 1",
+						},
+					}},
+					AssigneeTeam: &org_model.Team{
+						ID:   1,
+						Name: "Team 1",
+					},
+				},
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			issue := issues_model.Issue{
+				Comments: testCase.beforeCombined,
+			}
+			combineRequestReviewComments(&issue)
+			assert.EqualValues(t, testCase.afterCombined[0], issue.Comments[0])
+		})
+	}
+}
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index 08c83c07d7..78fa854dbe 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -522,35 +522,23 @@
 				</div>
 			</div>
 		{{else if eq .Type 27}}
-			<div class="timeline-item event" id="{{.HashTag}}">
-				<span class="badge">{{svg "octicon-eye"}}</span>
-				{{template "shared/user/avatarlink" dict "user" .Poster}}
-				<span class="text grey muted-links">
-					{{template "shared/user/authorlink" .Poster}}
-					{{if (gt .AssigneeID 0)}}
-						{{if .RemovedAssignee}}
-							{{if eq .PosterID .AssigneeID}}
-								{{ctx.Locale.Tr "repo.issues.review.remove_review_request_self" $createdStr}}
-							{{else}}
-								{{ctx.Locale.Tr "repo.issues.review.remove_review_request" .Assignee.GetDisplayName $createdStr}}
-							{{end}}
+				{{if or .AddedRequestReview .RemovedRequestReview}}
+				<div class="timeline-item event" id="{{.HashTag}}">
+					<span class="badge">{{svg "octicon-tag"}}</span>
+					{{template "shared/user/avatarlink" dict "user" .Poster}}
+					<span class="text grey muted-links">
+						{{if and (eq (len .RemovedRequestReview) 1) (eq (len .AddedRequestReview) 0) (eq ((index .RemovedRequestReview 0).ID) .PosterID) (eq ((index .RemovedRequestReview 0).Type) "user")}}
+							<span class="review-request-list">{{ctx.Locale.Tr "repo.issues.review.remove_review_request_self" $createdStr}}</span>
+						{{else if and .AddedRequestReview (not .RemovedRequestReview)}}
+							{{ctx.Locale.TrN (len .AddedRequestReview) "repo.issues.review.add_review_request" "repo.issues.review.add_review_requests" (RenderReviewRequest .AddedRequestReview) $createdStr}}
+						{{else if and (not .AddedRequestReview) .RemovedRequestReview}}
+							{{ctx.Locale.TrN (len .RemovedRequestReview) "repo.issues.review.remove_review_request" "repo.issues.review.remove_review_requests" (RenderReviewRequest .RemovedRequestReview) $createdStr}}
 						{{else}}
-							{{ctx.Locale.Tr "repo.issues.review.add_review_request" .Assignee.GetDisplayName $createdStr}}
+							{{ctx.Locale.Tr "repo.issues.review.add_remove_review_requests" (RenderReviewRequest .AddedRequestReview) (RenderReviewRequest .RemovedRequestReview) $createdStr}}
 						{{end}}
-					{{else}}
-						<!-- If the assigned team is deleted, just displaying "Ghost Team" in the comment -->
-						{{$teamName := "Ghost Team"}}
-						{{if .AssigneeTeam}}
-							{{$teamName = .AssigneeTeam.Name}}
-						{{end}}
-						{{if .RemovedAssignee}}
-							{{ctx.Locale.Tr "repo.issues.review.remove_review_request" $teamName $createdStr}}
-						{{else}}
-							{{ctx.Locale.Tr "repo.issues.review.add_review_request" $teamName $createdStr}}
-						{{end}}
-					{{end}}
-				</span>
-			</div>
+					</span>
+				</div>
+				{{end}}
 		{{else if and (eq .Type 29) (or (gt .CommitsNum 0) .IsForcePush)}}
 			<!-- If PR is closed, the comments whose type is CommentTypePullRequestPush(29) after latestCloseCommentID won't be rendered. //-->
 			{{if and .Issue.IsClosed (gt .ID $.LatestCloseCommentID)}}
diff --git a/tests/integration/fixtures/TestPullCombinedReviewRequest/review.yml b/tests/integration/fixtures/TestPullCombinedReviewRequest/review.yml
new file mode 100644
index 0000000000..deea4492e9
--- /dev/null
+++ b/tests/integration/fixtures/TestPullCombinedReviewRequest/review.yml
@@ -0,0 +1,21 @@
+-
+  id: 1001
+  type: 4
+  reviewer_id: 2
+  issue_id: 3
+  official: true
+  stale: false
+  original_author_id: 0
+  updated_unix: 1000000000
+  created_unix: 1000000000
+
+-
+  id: 1002
+  type: 4
+  reviewer_id: 11
+  issue_id: 3
+  official: true
+  stale: false
+  original_author_id: 0
+  updated_unix: 1000000000
+  created_unix: 1000000000
diff --git a/tests/integration/pull_test.go b/tests/integration/pull_test.go
index 10dbad2228..8f951f05ab 100644
--- a/tests/integration/pull_test.go
+++ b/tests/integration/pull_test.go
@@ -65,3 +65,37 @@ func TestPullManuallyMergeWarning(t *testing.T) {
 		assert.NotContains(t, mergeInstructions, warningMessage)
 	})
 }
+
+func TestPullCombinedReviewRequest(t *testing.T) {
+	defer tests.AddFixtures("tests/integration/fixtures/TestPullCombinedReviewRequest/")()
+	defer tests.PrepareTestEnv(t)()
+
+	session := loginUser(t, "user2")
+
+	helper := func(t *testing.T, action, userID, expectedText string) {
+		t.Helper()
+
+		req := NewRequestWithValues(t, "POST", "/user2/repo1/pulls/request_review", map[string]string{
+			"_csrf":     GetCSRF(t, session, "/user2/repo1/pulls/3"),
+			"issue_ids": "3",
+			"action":    action,
+			"id":        userID,
+		})
+		session.MakeRequest(t, req, http.StatusOK)
+
+		req = NewRequest(t, "GET", "/user2/repo1/pulls/3")
+		resp := session.MakeRequest(t, req, http.StatusOK)
+		htmlDoc := NewHTMLParser(t, resp.Body)
+
+		assert.Contains(t, htmlDoc.Find(".timeline-item:has(.review-request-list)").Last().Text(), expectedText)
+	}
+
+	helper(t, "detach", "2", "refused to review")
+	helper(t, "attach", "4", "requested reviews from user4 and removed review requests for user2")
+	helper(t, "attach", "9", "requested reviews from user4, user9 and removed review requests for user2")
+	helper(t, "attach", "2", "requested reviews from user4, user9")
+	helper(t, "detach", "4", "requested review from user9")
+	helper(t, "detach", "11", "requested reviews from user9 and removed review requests for user11")
+	helper(t, "detach", "9", "removed review request for user11")
+	helper(t, "detach", "2", "removed review requests for user11, user2")
+}