@@ -1389,13 +1389,17 @@ outer:
13891389 return review , nil
13901390}
13911391
1392- // CommentAsDiff returns c.Patch as *Diff
13931392// CommentAsDiff returns c.Patch as *Diff
13941393func CommentAsDiff (ctx context.Context , c * issues_model.Comment ) (* Diff , error ) {
1394+ // If patch is empty, try to generate it on-the-fly for unchanged line comments
1395+ if c .Patch == "" && c .TreePath != "" && c .CommitSHA != "" && c .Line != 0 {
1396+ return generateCodeContextForComment (ctx , c )
1397+ }
1398+
13951399 diff , err := ParsePatch (ctx , setting .Git .MaxGitDiffLines ,
13961400 setting .Git .MaxGitDiffLineCharacters , setting .Git .MaxGitDiffFiles , strings .NewReader (c .Patch ), "" )
13971401 if err != nil {
1398- log .Error ("Unable to parse patch: %v" , err )
1402+ log .Error ("Unable to parse patch for comment ID=%d : %v" , c . ID , err )
13991403 return nil , err
14001404 }
14011405 if len (diff .Files ) == 0 {
@@ -1408,6 +1412,97 @@ func CommentAsDiff(ctx context.Context, c *issues_model.Comment) (*Diff, error)
14081412 return diff , nil
14091413}
14101414
1415+ // generateCodeContextForComment creates a synthetic diff showing code context around a comment
1416+ func generateCodeContextForComment (ctx context.Context , c * issues_model.Comment ) (* Diff , error ) {
1417+ if err := c .LoadIssue (ctx ); err != nil {
1418+ return nil , fmt .Errorf ("LoadIssue: %w" , err )
1419+ }
1420+ if err := c .Issue .LoadRepo (ctx ); err != nil {
1421+ return nil , fmt .Errorf ("LoadRepo: %w" , err )
1422+ }
1423+ if err := c .Issue .LoadPullRequest (ctx ); err != nil {
1424+ return nil , fmt .Errorf ("LoadPullRequest: %w" , err )
1425+ }
1426+
1427+ pr := c .Issue .PullRequest
1428+ if err := pr .LoadBaseRepo (ctx ); err != nil {
1429+ return nil , fmt .Errorf ("LoadBaseRepo: %w" , err )
1430+ }
1431+
1432+ gitRepo , closer , err := gitrepo .RepositoryFromContextOrOpen (ctx , pr .BaseRepo )
1433+ if err != nil {
1434+ return nil , fmt .Errorf ("RepositoryFromContextOrOpen: %w" , err )
1435+ }
1436+ defer closer .Close ()
1437+
1438+ // Get the file content at the commit
1439+ commit , err := gitRepo .GetCommit (c .CommitSHA )
1440+ if err != nil {
1441+ return nil , fmt .Errorf ("GetCommit: %w" , err )
1442+ }
1443+
1444+ entry , err := commit .GetTreeEntryByPath (c .TreePath )
1445+ if err != nil {
1446+ return nil , fmt .Errorf ("GetTreeEntryByPath: %w" , err )
1447+ }
1448+
1449+ blob := entry .Blob ()
1450+ dataRc , err := blob .DataAsync ()
1451+ if err != nil {
1452+ return nil , fmt .Errorf ("DataAsync: %w" , err )
1453+ }
1454+ defer dataRc .Close ()
1455+
1456+ // Calculate line range (commented line + lines above it)
1457+ commentLine := int (c .UnsignedLine ())
1458+ contextLines := setting .UI .CodeCommentLines
1459+ startLine := max (commentLine - contextLines , 1 )
1460+ endLine := commentLine
1461+
1462+ // Read only the needed lines efficiently
1463+ scanner := bufio .NewScanner (dataRc )
1464+ currentLine := 0
1465+ var lines []string
1466+ for scanner .Scan () {
1467+ currentLine ++
1468+ if currentLine >= startLine && currentLine <= endLine {
1469+ lines = append (lines , scanner .Text ())
1470+ }
1471+ if currentLine > endLine {
1472+ break
1473+ }
1474+ }
1475+ if err := scanner .Err (); err != nil {
1476+ return nil , fmt .Errorf ("scanner error: %w" , err )
1477+ }
1478+
1479+ if len (lines ) == 0 {
1480+ return nil , fmt .Errorf ("no lines found in range %d-%d" , startLine , endLine )
1481+ }
1482+
1483+ // Generate synthetic patch
1484+ var patchBuilder strings.Builder
1485+ patchBuilder .WriteString (fmt .Sprintf ("diff --git a/%s b/%s\n " , c .TreePath , c .TreePath ))
1486+ patchBuilder .WriteString (fmt .Sprintf ("--- a/%s\n " , c .TreePath ))
1487+ patchBuilder .WriteString (fmt .Sprintf ("+++ b/%s\n " , c .TreePath ))
1488+ patchBuilder .WriteString (fmt .Sprintf ("@@ -%d,%d +%d,%d @@\n " , startLine , len (lines ), startLine , len (lines )))
1489+
1490+ for _ , lineContent := range lines {
1491+ patchBuilder .WriteString (" " )
1492+ patchBuilder .WriteString (lineContent )
1493+ patchBuilder .WriteString ("\n " )
1494+ }
1495+
1496+ // Parse the synthetic patch
1497+ diff , err := ParsePatch (ctx , setting .Git .MaxGitDiffLines ,
1498+ setting .Git .MaxGitDiffLineCharacters , setting .Git .MaxGitDiffFiles , strings .NewReader (patchBuilder .String ()), "" )
1499+ if err != nil {
1500+ return nil , fmt .Errorf ("ParsePatch: %w" , err )
1501+ }
1502+
1503+ return diff , nil
1504+ }
1505+
14111506// CommentMustAsDiff executes AsDiff and logs the error instead of returning
14121507func CommentMustAsDiff (ctx context.Context , c * issues_model.Comment ) * Diff {
14131508 if c == nil {
0 commit comments