Skip to content

Commit c30d74d

Browse files
brymutwxiaoguang
andauthored
feat(diff): Enable commenting on expanded lines in PR diffs (#35662)
Fixes #32257 /claim #32257 Implemented commenting on unchanged lines in Pull Request diffs, lines are accessed by expanding the diff preview. Comments also appear in the "Files Changed" tab on the unchanged lines where they were placed. --------- Co-authored-by: wxiaoguang <[email protected]>
1 parent 2d36a0c commit c30d74d

File tree

13 files changed

+753
-138
lines changed

13 files changed

+753
-138
lines changed

routers/web/repo/commit.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -276,20 +276,24 @@ func Diff(ctx *context.Context) {
276276
userName := ctx.Repo.Owner.Name
277277
repoName := ctx.Repo.Repository.Name
278278
commitID := ctx.PathParam("sha")
279-
var (
280-
gitRepo *git.Repository
281-
err error
282-
)
279+
280+
diffBlobExcerptData := &gitdiff.DiffBlobExcerptData{
281+
BaseLink: ctx.Repo.RepoLink + "/blob_excerpt",
282+
DiffStyle: ctx.FormString("style"),
283+
AfterCommitID: commitID,
284+
}
285+
gitRepo := ctx.Repo.GitRepo
286+
var gitRepoStore gitrepo.Repository = ctx.Repo.Repository
283287

284288
if ctx.Data["PageIsWiki"] != nil {
285-
gitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
289+
var err error
290+
gitRepoStore = ctx.Repo.Repository.WikiStorageRepo()
291+
gitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, gitRepoStore)
286292
if err != nil {
287293
ctx.ServerError("Repo.GitRepo.GetCommit", err)
288294
return
289295
}
290-
defer gitRepo.Close()
291-
} else {
292-
gitRepo = ctx.Repo.GitRepo
296+
diffBlobExcerptData.BaseLink = ctx.Repo.RepoLink + "/wiki/blob_excerpt"
293297
}
294298

295299
commit, err := gitRepo.GetCommit(commitID)
@@ -324,7 +328,7 @@ func Diff(ctx *context.Context) {
324328
ctx.NotFound(err)
325329
return
326330
}
327-
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, gitRepo, "", commitID)
331+
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, gitRepoStore, gitRepo, "", commitID)
328332
if err != nil {
329333
ctx.ServerError("GetDiffShortStat", err)
330334
return
@@ -360,6 +364,7 @@ func Diff(ctx *context.Context) {
360364
ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID)
361365
ctx.Data["Commit"] = commit
362366
ctx.Data["Diff"] = diff
367+
ctx.Data["DiffBlobExcerptData"] = diffBlobExcerptData
363368

364369
if !fileOnly {
365370
diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, parentCommitID, commitID)

routers/web/repo/compare.go

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"net/http"
1515
"net/url"
1616
"path/filepath"
17+
"sort"
1718
"strings"
1819

1920
"code.gitea.io/gitea/models/db"
@@ -43,6 +44,7 @@ import (
4344
"code.gitea.io/gitea/services/context/upload"
4445
"code.gitea.io/gitea/services/gitdiff"
4546
pull_service "code.gitea.io/gitea/services/pull"
47+
user_service "code.gitea.io/gitea/services/user"
4648
)
4749

4850
const (
@@ -638,6 +640,11 @@ func PrepareCompareDiff(
638640
}
639641
ctx.Data["DiffShortStat"] = diffShortStat
640642
ctx.Data["Diff"] = diff
643+
ctx.Data["DiffBlobExcerptData"] = &gitdiff.DiffBlobExcerptData{
644+
BaseLink: ci.HeadRepo.Link() + "/blob_excerpt",
645+
DiffStyle: ctx.FormString("style"),
646+
AfterCommitID: headCommitID,
647+
}
641648
ctx.Data["DiffNotAvailable"] = diffShortStat.NumFiles == 0
642649

643650
if !fileOnly {
@@ -865,6 +872,28 @@ func CompareDiff(ctx *context.Context) {
865872
ctx.HTML(http.StatusOK, tplCompare)
866873
}
867874

875+
// attachCommentsToLines attaches comments to their corresponding diff lines
876+
func attachCommentsToLines(section *gitdiff.DiffSection, lineComments map[int64][]*issues_model.Comment) {
877+
for _, line := range section.Lines {
878+
if comments, ok := lineComments[int64(line.LeftIdx*-1)]; ok {
879+
line.Comments = append(line.Comments, comments...)
880+
}
881+
if comments, ok := lineComments[int64(line.RightIdx)]; ok {
882+
line.Comments = append(line.Comments, comments...)
883+
}
884+
sort.SliceStable(line.Comments, func(i, j int) bool {
885+
return line.Comments[i].CreatedUnix < line.Comments[j].CreatedUnix
886+
})
887+
}
888+
}
889+
890+
// attachHiddenCommentIDs calculates and attaches hidden comment IDs to expand buttons
891+
func attachHiddenCommentIDs(section *gitdiff.DiffSection, lineComments map[int64][]*issues_model.Comment) {
892+
for _, line := range section.Lines {
893+
gitdiff.FillHiddenCommentIDsForDiffLine(line, lineComments)
894+
}
895+
}
896+
868897
// ExcerptBlob render blob excerpt contents
869898
func ExcerptBlob(ctx *context.Context) {
870899
commitID := ctx.PathParam("sha")
@@ -874,19 +903,26 @@ func ExcerptBlob(ctx *context.Context) {
874903
idxRight := ctx.FormInt("right")
875904
leftHunkSize := ctx.FormInt("left_hunk_size")
876905
rightHunkSize := ctx.FormInt("right_hunk_size")
877-
anchor := ctx.FormString("anchor")
878906
direction := ctx.FormString("direction")
879907
filePath := ctx.FormString("path")
880908
gitRepo := ctx.Repo.GitRepo
909+
910+
diffBlobExcerptData := &gitdiff.DiffBlobExcerptData{
911+
BaseLink: ctx.Repo.RepoLink + "/blob_excerpt",
912+
DiffStyle: ctx.FormString("style"),
913+
AfterCommitID: commitID,
914+
}
915+
881916
if ctx.Data["PageIsWiki"] == true {
882917
var err error
883-
gitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
918+
gitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository.WikiStorageRepo())
884919
if err != nil {
885920
ctx.ServerError("OpenRepository", err)
886921
return
887922
}
888-
defer gitRepo.Close()
923+
diffBlobExcerptData.BaseLink = ctx.Repo.RepoLink + "/wiki/blob_excerpt"
889924
}
925+
890926
chunkSize := gitdiff.BlobExcerptChunkSize
891927
commit, err := gitRepo.GetCommit(commitID)
892928
if err != nil {
@@ -947,10 +983,43 @@ func ExcerptBlob(ctx *context.Context) {
947983
section.Lines = append(section.Lines, lineSection)
948984
}
949985
}
986+
987+
diffBlobExcerptData.PullIssueIndex = ctx.FormInt64("pull_issue_index")
988+
if diffBlobExcerptData.PullIssueIndex > 0 {
989+
if !ctx.Repo.CanRead(unit.TypePullRequests) {
990+
ctx.NotFound(nil)
991+
return
992+
}
993+
994+
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, diffBlobExcerptData.PullIssueIndex)
995+
if err != nil {
996+
log.Error("GetIssueByIndex error: %v", err)
997+
} else if issue.IsPull {
998+
// FIXME: DIFF-CONVERSATION-DATA: the following data assignment is fragile
999+
ctx.Data["Issue"] = issue
1000+
ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool {
1001+
return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee)
1002+
}
1003+
// and "diff/comment_form.tmpl" (reply comment) needs them
1004+
ctx.Data["PageIsPullFiles"] = true
1005+
ctx.Data["AfterCommitID"] = diffBlobExcerptData.AfterCommitID
1006+
1007+
allComments, err := issues_model.FetchCodeComments(ctx, issue, ctx.Doer, ctx.FormBool("show_outdated"))
1008+
if err != nil {
1009+
log.Error("FetchCodeComments error: %v", err)
1010+
} else {
1011+
if lineComments, ok := allComments[filePath]; ok {
1012+
attachCommentsToLines(section, lineComments)
1013+
attachHiddenCommentIDs(section, lineComments)
1014+
}
1015+
}
1016+
}
1017+
}
1018+
9501019
ctx.Data["section"] = section
9511020
ctx.Data["FileNameHash"] = git.HashFilePathForWebUI(filePath)
952-
ctx.Data["AfterCommitID"] = commitID
953-
ctx.Data["Anchor"] = anchor
1021+
ctx.Data["DiffBlobExcerptData"] = diffBlobExcerptData
1022+
9541023
ctx.HTML(http.StatusOK, tplBlobExcerpt)
9551024
}
9561025

routers/web/repo/compare_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package repo
5+
6+
import (
7+
"testing"
8+
9+
issues_model "code.gitea.io/gitea/models/issues"
10+
"code.gitea.io/gitea/services/gitdiff"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestAttachCommentsToLines(t *testing.T) {
16+
section := &gitdiff.DiffSection{
17+
Lines: []*gitdiff.DiffLine{
18+
{LeftIdx: 5, RightIdx: 10},
19+
{LeftIdx: 6, RightIdx: 11},
20+
},
21+
}
22+
23+
lineComments := map[int64][]*issues_model.Comment{
24+
-5: {{ID: 100, CreatedUnix: 1000}}, // left side comment
25+
10: {{ID: 200, CreatedUnix: 2000}}, // right side comment
26+
11: {{ID: 300, CreatedUnix: 1500}, {ID: 301, CreatedUnix: 2500}}, // multiple comments
27+
}
28+
29+
attachCommentsToLines(section, lineComments)
30+
31+
// First line should have left and right comments
32+
assert.Len(t, section.Lines[0].Comments, 2)
33+
assert.Equal(t, int64(100), section.Lines[0].Comments[0].ID)
34+
assert.Equal(t, int64(200), section.Lines[0].Comments[1].ID)
35+
36+
// Second line should have two comments, sorted by creation time
37+
assert.Len(t, section.Lines[1].Comments, 2)
38+
assert.Equal(t, int64(300), section.Lines[1].Comments[0].ID)
39+
assert.Equal(t, int64(301), section.Lines[1].Comments[1].ID)
40+
}

routers/web/repo/pull.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,12 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
827827
}
828828

829829
ctx.Data["Diff"] = diff
830+
ctx.Data["DiffBlobExcerptData"] = &gitdiff.DiffBlobExcerptData{
831+
BaseLink: ctx.Repo.RepoLink + "/blob_excerpt",
832+
PullIssueIndex: pull.Index,
833+
DiffStyle: ctx.FormString("style"),
834+
AfterCommitID: afterCommitID,
835+
}
830836
ctx.Data["DiffNotAvailable"] = diffShortStat.NumFiles == 0
831837

832838
if ctx.IsSigned && ctx.Doer != nil {

0 commit comments

Comments
 (0)