@@ -81,13 +81,15 @@ const (
8181
8282// DiffLine represents a line difference in a DiffSection.
8383type DiffLine struct {
84- LeftIdx int // line number, 1-based
85- RightIdx int // line number, 1-based
86- Match int // the diff matched index. -1: no match. 0: plain and no need to match. >0: for add/del, "Lines" slice index of the other side
87- Type DiffLineType
88- Content string
89- Comments issues_model.CommentList // related PR code comments
90- SectionInfo * DiffLineSectionInfo
84+ LeftIdx int // line number, 1-based
85+ RightIdx int // line number, 1-based
86+ Match int // the diff matched index. -1: no match. 0: plain and no need to match. >0: for add/del, "Lines" slice index of the other side
87+ Type DiffLineType
88+ Content string
89+ Comments issues_model.CommentList // related PR code comments
90+ SectionInfo * DiffLineSectionInfo
91+ HasHiddenComments bool // indicates if this expand button has comments in hidden lines
92+ HiddenCommentCount int // number of hidden comments in this section
9193}
9294
9395// DiffLineSectionInfo represents diff line section meta data
@@ -485,6 +487,25 @@ func (diff *Diff) LoadComments(ctx context.Context, issue *issues_model.Issue, c
485487 sort .SliceStable (line .Comments , func (i , j int ) bool {
486488 return line .Comments [i ].CreatedUnix < line .Comments [j ].CreatedUnix
487489 })
490+
491+ // Mark expand buttons that have comments in hidden lines
492+ if line .Type == DiffLineSection && line .SectionInfo != nil {
493+ hiddenCommentCount := 0
494+ // Check if there are comments in the hidden range
495+ for commentLineNum := range lineCommits {
496+ absLineNum := int (commentLineNum )
497+ if commentLineNum < 0 {
498+ absLineNum = int (- commentLineNum )
499+ }
500+ if absLineNum > line .SectionInfo .LastRightIdx && absLineNum < line .SectionInfo .RightIdx {
501+ hiddenCommentCount ++
502+ }
503+ }
504+ if hiddenCommentCount > 0 {
505+ line .HasHiddenComments = true
506+ line .HiddenCommentCount = hiddenCommentCount
507+ }
508+ }
488509 }
489510 }
490511 }
@@ -1370,19 +1391,105 @@ outer:
13701391
13711392// CommentAsDiff returns c.Patch as *Diff
13721393func CommentAsDiff (ctx context.Context , c * issues_model.Comment ) (* Diff , error ) {
1373- diff , err := ParsePatch (ctx , setting .Git .MaxGitDiffLines ,
1374- setting .Git .MaxGitDiffLineCharacters , setting .Git .MaxGitDiffFiles , strings .NewReader (c .Patch ), "" )
1394+ // Try to parse existing patch first
1395+ if c .Patch != "" {
1396+ diff , err := ParsePatch (ctx , setting .Git .MaxGitDiffLines ,
1397+ setting .Git .MaxGitDiffLineCharacters , setting .Git .MaxGitDiffFiles , strings .NewReader (c .Patch ), "" )
1398+ if err == nil && len (diff .Files ) > 0 && len (diff .Files [0 ].Sections ) > 0 {
1399+ return diff , nil
1400+ }
1401+ }
1402+
1403+ // If patch is empty or invalid, generate code context for unchanged line comments
1404+ if c .TreePath != "" && c .CommitSHA != "" && c .Line != 0 {
1405+ return generateCodeContextForComment (ctx , c )
1406+ }
1407+
1408+ return nil , fmt .Errorf ("no valid patch or context available for comment ID: %d" , c .ID )
1409+ }
1410+
1411+ func generateCodeContextForComment (ctx context.Context , c * issues_model.Comment ) (* Diff , error ) {
1412+ if err := c .LoadIssue (ctx ); err != nil {
1413+ return nil , fmt .Errorf ("LoadIssue: %w" , err )
1414+ }
1415+ if err := c .Issue .LoadRepo (ctx ); err != nil {
1416+ return nil , fmt .Errorf ("LoadRepo: %w" , err )
1417+ }
1418+ if err := c .Issue .LoadPullRequest (ctx ); err != nil {
1419+ return nil , fmt .Errorf ("LoadPullRequest: %w" , err )
1420+ }
1421+
1422+ pr := c .Issue .PullRequest
1423+ if err := pr .LoadBaseRepo (ctx ); err != nil {
1424+ return nil , fmt .Errorf ("LoadBaseRepo: %w" , err )
1425+ }
1426+
1427+ gitRepo , closer , err := gitrepo .RepositoryFromContextOrOpen (ctx , pr .BaseRepo )
13751428 if err != nil {
1376- log .Error ("Unable to parse patch: %v" , err )
1377- return nil , err
1429+ return nil , fmt .Errorf ("RepositoryFromContextOrOpen: %w" , err )
1430+ }
1431+ defer closer .Close ()
1432+
1433+ // Get the file content at the commit
1434+ commit , err := gitRepo .GetCommit (c .CommitSHA )
1435+ if err != nil {
1436+ return nil , fmt .Errorf ("GetCommit: %w" , err )
13781437 }
1379- if len (diff .Files ) == 0 {
1380- return nil , fmt .Errorf ("no file found for comment ID: %d" , c .ID )
1438+
1439+ entry , err := commit .GetTreeEntryByPath (c .TreePath )
1440+ if err != nil {
1441+ return nil , fmt .Errorf ("GetTreeEntryByPath: %w" , err )
13811442 }
1382- secs := diff .Files [0 ].Sections
1383- if len (secs ) == 0 {
1384- return nil , fmt .Errorf ("no sections found for comment ID: %d" , c .ID )
1443+
1444+ blob := entry .Blob ()
1445+ dataRc , err := blob .DataAsync ()
1446+ if err != nil {
1447+ return nil , fmt .Errorf ("DataAsync: %w" , err )
1448+ }
1449+ defer dataRc .Close ()
1450+
1451+ // Read file lines
1452+ scanner := bufio .NewScanner (dataRc )
1453+ var lines []string
1454+ for scanner .Scan () {
1455+ lines = append (lines , scanner .Text ())
1456+ }
1457+ if err := scanner .Err (); err != nil {
1458+ return nil , fmt .Errorf ("scanner error: %w" , err )
1459+ }
1460+
1461+ // Validate comment line is within file bounds
1462+ commentLine := int (c .UnsignedLine ())
1463+ if commentLine < 1 || commentLine > len (lines ) {
1464+ return nil , fmt .Errorf ("comment line %d is out of file bounds (1-%d)" , commentLine , len (lines ))
13851465 }
1466+
1467+ // Calculate line range to show (commented line + lines above it, matching CutDiffAroundLine behavior)
1468+ contextLines := setting .UI .CodeCommentLines
1469+ startLine := commentLine - contextLines
1470+ if startLine < 1 {
1471+ startLine = 1
1472+ }
1473+ endLine := commentLine
1474+
1475+ var patchBuilder strings.Builder
1476+ patchBuilder .WriteString (fmt .Sprintf ("diff --git a/%s b/%s\n " , c .TreePath , c .TreePath ))
1477+ patchBuilder .WriteString (fmt .Sprintf ("--- a/%s\n " , c .TreePath ))
1478+ patchBuilder .WriteString (fmt .Sprintf ("+++ b/%s\n " , c .TreePath ))
1479+ patchBuilder .WriteString (fmt .Sprintf ("@@ -%d,%d +%d,%d @@\n " , startLine , endLine - startLine + 1 , startLine , endLine - startLine + 1 ))
1480+
1481+ for i := startLine - 1 ; i < endLine ; i ++ {
1482+ patchBuilder .WriteString (" " )
1483+ patchBuilder .WriteString (lines [i ])
1484+ patchBuilder .WriteString ("\n " )
1485+ }
1486+
1487+ diff , err := ParsePatch (ctx , setting .Git .MaxGitDiffLines ,
1488+ setting .Git .MaxGitDiffLineCharacters , setting .Git .MaxGitDiffFiles , strings .NewReader (patchBuilder .String ()), "" )
1489+ if err != nil {
1490+ return nil , fmt .Errorf ("ParsePatch: %w" , err )
1491+ }
1492+
13861493 return diff , nil
13871494}
13881495
0 commit comments