Skip to content

Commit 497f690

Browse files
committed
feat(gitdiff): fix code context in conversation tab
1 parent 30e3fd1 commit 497f690

File tree

2 files changed

+98
-6
lines changed

2 files changed

+98
-6
lines changed

services/gitdiff/gitdiff.go

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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
13941393
func 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
14121507
func CommentMustAsDiff(ctx context.Context, c *issues_model.Comment) *Diff {
14131508
if c == nil {

services/pull/review.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,7 @@ func generatePatchForUnchangedLine(gitRepo *git.Repository, commitID, treePath s
222222
if line < 0 {
223223
commentLine = int(-line)
224224
}
225-
startLine := commentLine - contextLines
226-
if startLine < 1 {
227-
startLine = 1
228-
}
225+
startLine := max(commentLine-contextLines, 1)
229226
endLine := commentLine
230227

231228
// Read only the needed lines efficiently

0 commit comments

Comments
 (0)