Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
17be181
feat(diff): Enable commenting on expanded lines in PR diffs
brymut Oct 14, 2025
1b9ac2c
feat(gitdiff): Use generatePatchForUnchangedLine instead to generate …
brymut Oct 16, 2025
8216008
fix(PR): Add permission check
brymut Oct 16, 2025
30e3fd1
fix(diff): update hidden comment count on code expand buttons
brymut Oct 16, 2025
b234f31
feat(gitdiff): fix code context in conversation tab
brymut Oct 16, 2025
4b85c50
fix(gitdiff): remove patching logic duplication
brymut Oct 17, 2025
2c33c7d
feat(diff): Enhance hidden comment expansion when sharing link
brymut Oct 18, 2025
19ed046
fix: highlighting to shared hidden comments
brymut Oct 18, 2025
3e0c892
test(diff): add tests for helper functions in ExcerptBlob
brymut Oct 18, 2025
aafa231
refactor(gitdiff): Extract hidden comment calculation to shared metho…
brymut Oct 18, 2025
fd2fc61
clean up code-expander-button
wxiaoguang Oct 18, 2025
a95ebbb
fix
wxiaoguang Oct 18, 2025
38d40a6
fix tmpl var
wxiaoguang Oct 18, 2025
6408c43
add missing comment
wxiaoguang Oct 18, 2025
bbd4799
fine tune tests
wxiaoguang Oct 18, 2025
c0b8201
remove unnecessary code
wxiaoguang Oct 18, 2025
590df91
fix: handle righthunksize == 0 scenario
brymut Oct 18, 2025
d1fc0d7
fine tune
wxiaoguang Oct 19, 2025
9356dbe
fix: remove reduntant test
brymut Oct 19, 2025
78cd2ce
fix(gitdiff): fix line number handling in `FillHiddenCommentIDsForDif…
brymut Oct 19, 2025
bb714b4
fine tune
wxiaoguang Oct 19, 2025
c5c1c2b
Merge branch 'main' into enable-commenting-unchanged-line
wxiaoguang Oct 19, 2025
af864c2
fix assumption
wxiaoguang Oct 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions routers/web/repo/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
79 changes: 74 additions & 5 deletions routers/web/repo/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"net/http"
"net/url"
"path/filepath"
"sort"
"strings"

"code.gitea.io/gitea/models/db"
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}

Expand Down
40 changes: 40 additions & 0 deletions routers/web/repo/compare_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
6 changes: 6 additions & 0 deletions routers/web/repo/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading