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 <wxiaoguang@gmail.com>
This commit is contained in:
@@ -276,20 +276,24 @@ func Diff(ctx *context.Context) {
|
||||
userName := ctx.Repo.Owner.Name
|
||||
repoName := ctx.Repo.Repository.Name
|
||||
commitID := ctx.PathParam("sha")
|
||||
var (
|
||||
gitRepo *git.Repository
|
||||
err error
|
||||
)
|
||||
|
||||
diffBlobExcerptData := &gitdiff.DiffBlobExcerptData{
|
||||
BaseLink: ctx.Repo.RepoLink + "/blob_excerpt",
|
||||
DiffStyle: ctx.FormString("style"),
|
||||
AfterCommitID: commitID,
|
||||
}
|
||||
gitRepo := ctx.Repo.GitRepo
|
||||
var gitRepoStore gitrepo.Repository = ctx.Repo.Repository
|
||||
|
||||
if ctx.Data["PageIsWiki"] != nil {
|
||||
gitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
|
||||
var err error
|
||||
gitRepoStore = ctx.Repo.Repository.WikiStorageRepo()
|
||||
gitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, gitRepoStore)
|
||||
if err != nil {
|
||||
ctx.ServerError("Repo.GitRepo.GetCommit", err)
|
||||
return
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
} else {
|
||||
gitRepo = ctx.Repo.GitRepo
|
||||
diffBlobExcerptData.BaseLink = ctx.Repo.RepoLink + "/wiki/blob_excerpt"
|
||||
}
|
||||
|
||||
commit, err := gitRepo.GetCommit(commitID)
|
||||
@@ -324,7 +328,7 @@ func Diff(ctx *context.Context) {
|
||||
ctx.NotFound(err)
|
||||
return
|
||||
}
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, gitRepo, "", commitID)
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, gitRepoStore, gitRepo, "", commitID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDiffShortStat", err)
|
||||
return
|
||||
@@ -360,6 +364,7 @@ func Diff(ctx *context.Context) {
|
||||
ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID)
|
||||
ctx.Data["Commit"] = commit
|
||||
ctx.Data["Diff"] = diff
|
||||
ctx.Data["DiffBlobExcerptData"] = diffBlobExcerptData
|
||||
|
||||
if !fileOnly {
|
||||
diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, parentCommitID, commitID)
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@@ -43,6 +44,7 @@ import (
|
||||
"code.gitea.io/gitea/services/context/upload"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -638,6 +640,11 @@ func PrepareCompareDiff(
|
||||
}
|
||||
ctx.Data["DiffShortStat"] = diffShortStat
|
||||
ctx.Data["Diff"] = diff
|
||||
ctx.Data["DiffBlobExcerptData"] = &gitdiff.DiffBlobExcerptData{
|
||||
BaseLink: ci.HeadRepo.Link() + "/blob_excerpt",
|
||||
DiffStyle: ctx.FormString("style"),
|
||||
AfterCommitID: headCommitID,
|
||||
}
|
||||
ctx.Data["DiffNotAvailable"] = diffShortStat.NumFiles == 0
|
||||
|
||||
if !fileOnly {
|
||||
@@ -865,6 +872,28 @@ func CompareDiff(ctx *context.Context) {
|
||||
ctx.HTML(http.StatusOK, tplCompare)
|
||||
}
|
||||
|
||||
// attachCommentsToLines attaches comments to their corresponding diff lines
|
||||
func attachCommentsToLines(section *gitdiff.DiffSection, lineComments map[int64][]*issues_model.Comment) {
|
||||
for _, line := range section.Lines {
|
||||
if comments, ok := lineComments[int64(line.LeftIdx*-1)]; ok {
|
||||
line.Comments = append(line.Comments, comments...)
|
||||
}
|
||||
if comments, ok := lineComments[int64(line.RightIdx)]; ok {
|
||||
line.Comments = append(line.Comments, comments...)
|
||||
}
|
||||
sort.SliceStable(line.Comments, func(i, j int) bool {
|
||||
return line.Comments[i].CreatedUnix < line.Comments[j].CreatedUnix
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// attachHiddenCommentIDs calculates and attaches hidden comment IDs to expand buttons
|
||||
func attachHiddenCommentIDs(section *gitdiff.DiffSection, lineComments map[int64][]*issues_model.Comment) {
|
||||
for _, line := range section.Lines {
|
||||
gitdiff.FillHiddenCommentIDsForDiffLine(line, lineComments)
|
||||
}
|
||||
}
|
||||
|
||||
// ExcerptBlob render blob excerpt contents
|
||||
func ExcerptBlob(ctx *context.Context) {
|
||||
commitID := ctx.PathParam("sha")
|
||||
@@ -874,19 +903,26 @@ func ExcerptBlob(ctx *context.Context) {
|
||||
idxRight := ctx.FormInt("right")
|
||||
leftHunkSize := ctx.FormInt("left_hunk_size")
|
||||
rightHunkSize := ctx.FormInt("right_hunk_size")
|
||||
anchor := ctx.FormString("anchor")
|
||||
direction := ctx.FormString("direction")
|
||||
filePath := ctx.FormString("path")
|
||||
gitRepo := ctx.Repo.GitRepo
|
||||
|
||||
diffBlobExcerptData := &gitdiff.DiffBlobExcerptData{
|
||||
BaseLink: ctx.Repo.RepoLink + "/blob_excerpt",
|
||||
DiffStyle: ctx.FormString("style"),
|
||||
AfterCommitID: commitID,
|
||||
}
|
||||
|
||||
if ctx.Data["PageIsWiki"] == true {
|
||||
var err error
|
||||
gitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
|
||||
gitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository.WikiStorageRepo())
|
||||
if err != nil {
|
||||
ctx.ServerError("OpenRepository", err)
|
||||
return
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
diffBlobExcerptData.BaseLink = ctx.Repo.RepoLink + "/wiki/blob_excerpt"
|
||||
}
|
||||
|
||||
chunkSize := gitdiff.BlobExcerptChunkSize
|
||||
commit, err := gitRepo.GetCommit(commitID)
|
||||
if err != nil {
|
||||
@@ -947,10 +983,43 @@ func ExcerptBlob(ctx *context.Context) {
|
||||
section.Lines = append(section.Lines, lineSection)
|
||||
}
|
||||
}
|
||||
|
||||
diffBlobExcerptData.PullIssueIndex = ctx.FormInt64("pull_issue_index")
|
||||
if diffBlobExcerptData.PullIssueIndex > 0 {
|
||||
if !ctx.Repo.CanRead(unit.TypePullRequests) {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, diffBlobExcerptData.PullIssueIndex)
|
||||
if err != nil {
|
||||
log.Error("GetIssueByIndex error: %v", err)
|
||||
} else if issue.IsPull {
|
||||
// FIXME: DIFF-CONVERSATION-DATA: the following data assignment is fragile
|
||||
ctx.Data["Issue"] = issue
|
||||
ctx.Data["CanBlockUser"] = func(blocker, blockee *user_model.User) bool {
|
||||
return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee)
|
||||
}
|
||||
// and "diff/comment_form.tmpl" (reply comment) needs them
|
||||
ctx.Data["PageIsPullFiles"] = true
|
||||
ctx.Data["AfterCommitID"] = diffBlobExcerptData.AfterCommitID
|
||||
|
||||
allComments, err := issues_model.FetchCodeComments(ctx, issue, ctx.Doer, ctx.FormBool("show_outdated"))
|
||||
if err != nil {
|
||||
log.Error("FetchCodeComments error: %v", err)
|
||||
} else {
|
||||
if lineComments, ok := allComments[filePath]; ok {
|
||||
attachCommentsToLines(section, lineComments)
|
||||
attachHiddenCommentIDs(section, lineComments)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["section"] = section
|
||||
ctx.Data["FileNameHash"] = git.HashFilePathForWebUI(filePath)
|
||||
ctx.Data["AfterCommitID"] = commitID
|
||||
ctx.Data["Anchor"] = anchor
|
||||
ctx.Data["DiffBlobExcerptData"] = diffBlobExcerptData
|
||||
|
||||
ctx.HTML(http.StatusOK, tplBlobExcerpt)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAttachCommentsToLines(t *testing.T) {
|
||||
section := &gitdiff.DiffSection{
|
||||
Lines: []*gitdiff.DiffLine{
|
||||
{LeftIdx: 5, RightIdx: 10},
|
||||
{LeftIdx: 6, RightIdx: 11},
|
||||
},
|
||||
}
|
||||
|
||||
lineComments := map[int64][]*issues_model.Comment{
|
||||
-5: {{ID: 100, CreatedUnix: 1000}}, // left side comment
|
||||
10: {{ID: 200, CreatedUnix: 2000}}, // right side comment
|
||||
11: {{ID: 300, CreatedUnix: 1500}, {ID: 301, CreatedUnix: 2500}}, // multiple comments
|
||||
}
|
||||
|
||||
attachCommentsToLines(section, lineComments)
|
||||
|
||||
// First line should have left and right comments
|
||||
assert.Len(t, section.Lines[0].Comments, 2)
|
||||
assert.Equal(t, int64(100), section.Lines[0].Comments[0].ID)
|
||||
assert.Equal(t, int64(200), section.Lines[0].Comments[1].ID)
|
||||
|
||||
// Second line should have two comments, sorted by creation time
|
||||
assert.Len(t, section.Lines[1].Comments, 2)
|
||||
assert.Equal(t, int64(300), section.Lines[1].Comments[0].ID)
|
||||
assert.Equal(t, int64(301), section.Lines[1].Comments[1].ID)
|
||||
}
|
||||
@@ -827,6 +827,12 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
|
||||
}
|
||||
|
||||
ctx.Data["Diff"] = diff
|
||||
ctx.Data["DiffBlobExcerptData"] = &gitdiff.DiffBlobExcerptData{
|
||||
BaseLink: ctx.Repo.RepoLink + "/blob_excerpt",
|
||||
PullIssueIndex: pull.Index,
|
||||
DiffStyle: ctx.FormString("style"),
|
||||
AfterCommitID: afterCommitID,
|
||||
}
|
||||
ctx.Data["DiffNotAvailable"] = diffShortStat.NumFiles == 0
|
||||
|
||||
if ctx.IsSigned && ctx.Doer != nil {
|
||||
|
||||
Reference in New Issue
Block a user