Skip to content

Commit a18cdea

Browse files
committed
CutDiffAroundLine makes the incorrect assumption that --- and +++ always represent part of the header of a diff.
This PR adds a flag to its parsing to prevent this problem and adds a streaming parsing technique to CutDiffAroundLine using an io.pipe instead of just sending data to an unbounded buffer. Fix #14711 Signed-off-by: Andrew Thornton <[email protected]>
1 parent 42118c6 commit a18cdea

File tree

4 files changed

+107
-27
lines changed

4 files changed

+107
-27
lines changed

modules/git/diff.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,30 +125,40 @@ var hunkRegex = regexp.MustCompile(`^@@ -(?P<beginOld>[0-9]+)(,(?P<endOld>[0-9]+
125125

126126
const cmdDiffHead = "diff --git "
127127

128-
func isHeader(lof string) bool {
129-
return strings.HasPrefix(lof, cmdDiffHead) || strings.HasPrefix(lof, "---") || strings.HasPrefix(lof, "+++")
128+
func isHeader(lof string, inHunk bool) bool {
129+
130+
return strings.HasPrefix(lof, cmdDiffHead) || (!inHunk && (strings.HasPrefix(lof, "---") || strings.HasPrefix(lof, "+++")))
130131
}
131132

132133
// CutDiffAroundLine cuts a diff of a file in way that only the given line + numberOfLine above it will be shown
133134
// it also recalculates hunks and adds the appropriate headers to the new diff.
134135
// Warning: Only one-file diffs are allowed.
135-
func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLine int) string {
136+
func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLine int) (string, error) {
136137
if line == 0 || numbersOfLine == 0 {
137138
// no line or num of lines => no diff
138-
return ""
139+
return "", nil
139140
}
141+
140142
scanner := bufio.NewScanner(originalDiff)
141143
hunk := make([]string, 0)
144+
142145
// begin is the start of the hunk containing searched line
143146
// end is the end of the hunk ...
144147
// currentLine is the line number on the side of the searched line (differentiated by old)
145148
// otherLine is the line number on the opposite side of the searched line (differentiated by old)
146149
var begin, end, currentLine, otherLine int64
147150
var headerLines int
151+
152+
inHunk := false
153+
148154
for scanner.Scan() {
149155
lof := scanner.Text()
150156
// Add header to enable parsing
151-
if isHeader(lof) {
157+
158+
if isHeader(lof, inHunk) {
159+
if strings.HasPrefix(lof, cmdDiffHead) {
160+
inHunk = false
161+
}
152162
hunk = append(hunk, lof)
153163
headerLines++
154164
}
@@ -157,6 +167,7 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
157167
}
158168
// Detect "hunk" with contains commented lof
159169
if strings.HasPrefix(lof, "@@") {
170+
inHunk = true
160171
// Already got our hunk. End of hunk detected!
161172
if len(hunk) > headerLines {
162173
break
@@ -213,15 +224,19 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
213224
}
214225
}
215226
}
227+
err := scanner.Err()
228+
if err != nil {
229+
return "", err
230+
}
216231

217232
// No hunk found
218233
if currentLine == 0 {
219-
return ""
234+
return "", nil
220235
}
221236
// headerLines + hunkLine (1) = totalNonCodeLines
222237
if len(hunk)-headerLines-1 <= numbersOfLine {
223238
// No need to cut the hunk => return existing hunk
224-
return strings.Join(hunk, "\n")
239+
return strings.Join(hunk, "\n"), nil
225240
}
226241
var oldBegin, oldNumOfLines, newBegin, newNumOfLines int64
227242
if old {
@@ -256,5 +271,5 @@ func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLi
256271
// construct the new hunk header
257272
newHunk[headerLines] = fmt.Sprintf("@@ -%d,%d +%d,%d @@",
258273
oldBegin, oldNumOfLines, newBegin, newNumOfLines)
259-
return strings.Join(newHunk, "\n")
274+
return strings.Join(newHunk, "\n"), nil
260275
}

modules/git/diff_test.go

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,27 @@ const exampleDiff = `diff --git a/README.md b/README.md
2323
+ cut off
2424
+ cut off`
2525

26+
const breakingDiff = `diff --git a/aaa.sql b/aaa.sql
27+
index d8e4c92..19dc8ad 100644
28+
--- a/aaa.sql
29+
+++ b/aaa.sql
30+
@@ -1,9 +1,10 @@
31+
--some comment
32+
--- some comment 5
33+
+--some coment 2
34+
+-- some comment 3
35+
create or replace procedure test(p1 varchar2)
36+
is
37+
begin
38+
---new comment
39+
dbms_output.put_line(p1);
40+
+--some other comment
41+
end;
42+
/
43+
`
44+
2645
func TestCutDiffAroundLine(t *testing.T) {
27-
result := CutDiffAroundLine(strings.NewReader(exampleDiff), 4, false, 3)
46+
result, _ := CutDiffAroundLine(strings.NewReader(exampleDiff), 4, false, 3)
2847
resultByLine := strings.Split(result, "\n")
2948
assert.Len(t, resultByLine, 7)
3049
// Check if headers got transferred
@@ -37,18 +56,44 @@ func TestCutDiffAroundLine(t *testing.T) {
3756
assert.Equal(t, "+ Build Status", resultByLine[4])
3857

3958
// Must be same result as before since old line 3 == new line 5
40-
newResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3)
59+
newResult, _ := CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3)
4160
assert.Equal(t, result, newResult, "Must be same result as before since old line 3 == new line 5")
4261

43-
newResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 300)
62+
newResult, _ = CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 300)
4463
assert.Equal(t, exampleDiff, newResult)
4564

46-
emptyResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 0)
65+
emptyResult, _ := CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 0)
4766
assert.Empty(t, emptyResult)
4867

4968
// Line is out of scope
50-
emptyResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 434, false, 0)
69+
emptyResult, _ = CutDiffAroundLine(strings.NewReader(exampleDiff), 434, false, 0)
5170
assert.Empty(t, emptyResult)
71+
72+
// Handle minus diffs properly
73+
minusDiff, _ := CutDiffAroundLine(strings.NewReader(breakingDiff), 2, false, 4)
74+
75+
expected := `diff --git a/aaa.sql b/aaa.sql
76+
--- a/aaa.sql
77+
+++ b/aaa.sql
78+
@@ -1,9 +1,10 @@
79+
--some comment
80+
--- some comment 5
81+
+--some coment 2`
82+
assert.Equal(t, expected, minusDiff)
83+
84+
// Handle minus diffs properly
85+
minusDiff, _ = CutDiffAroundLine(strings.NewReader(breakingDiff), 3, false, 4)
86+
87+
expected = `diff --git a/aaa.sql b/aaa.sql
88+
--- a/aaa.sql
89+
+++ b/aaa.sql
90+
@@ -1,9 +1,10 @@
91+
--some comment
92+
--- some comment 5
93+
+--some coment 2
94+
+-- some comment 3`
95+
96+
assert.Equal(t, expected, minusDiff)
5297
}
5398

5499
func BenchmarkCutDiffAroundLine(b *testing.B) {
@@ -69,7 +114,7 @@ func ExampleCutDiffAroundLine() {
69114
Docker Pulls
70115
+ cut off
71116
+ cut off`
72-
result := CutDiffAroundLine(strings.NewReader(diff), 4, false, 3)
117+
result, _ := CutDiffAroundLine(strings.NewReader(diff), 4, false, 3)
73118
println(result)
74119
}
75120

modules/migrations/gitea_uploader.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package migrations
77

88
import (
9-
"bytes"
109
"context"
1110
"fmt"
1211
"io"
@@ -802,13 +801,20 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
802801
}
803802

804803
var patch string
805-
patchBuf := new(bytes.Buffer)
806-
if err := git.GetRepoRawDiffForFile(g.gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, comment.TreePath, patchBuf); err != nil {
807-
// We should ignore the error since the commit maybe removed when force push to the pull request
808-
log.Warn("GetRepoRawDiffForFile failed when migrating [%s, %s, %s, %s]: %v", g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath, err)
809-
} else {
810-
patch = git.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
811-
}
804+
reader, writer := io.Pipe()
805+
defer func() {
806+
_ = reader.Close()
807+
_ = writer.Close()
808+
}()
809+
go func() {
810+
if err := git.GetRepoRawDiffForFile(g.gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, comment.TreePath, writer); err != nil {
811+
// We should ignore the error since the commit maybe removed when force push to the pull request
812+
log.Warn("GetRepoRawDiffForFile failed when migrating [%s, %s, %s, %s]: %v", g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath, err)
813+
}
814+
_ = writer.Close()
815+
}()
816+
817+
patch, _ = git.CutDiffAroundLine(reader, int64((&models.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
812818

813819
var c = models.Comment{
814820
Type: models.CommentTypeCode,

services/pull/review.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
package pull
77

88
import (
9-
"bytes"
109
"fmt"
10+
"io"
1111
"regexp"
1212
"strings"
1313

1414
"code.gitea.io/gitea/models"
1515
"code.gitea.io/gitea/modules/git"
16+
"code.gitea.io/gitea/modules/log"
1617
"code.gitea.io/gitea/modules/notification"
1718
"code.gitea.io/gitea/modules/setting"
1819
)
@@ -179,11 +180,24 @@ func createCodeComment(doer *models.User, repo *models.Repository, issue *models
179180
if len(commitID) == 0 {
180181
commitID = headCommitID
181182
}
182-
patchBuf := new(bytes.Buffer)
183-
if err := git.GetRepoRawDiffForFile(gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, treePath, patchBuf); err != nil {
184-
return nil, fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", gitRepo.Path, pr.MergeBase, headCommitID, treePath, err)
183+
reader, writer := io.Pipe()
184+
defer func() {
185+
_ = reader.Close()
186+
_ = writer.Close()
187+
}()
188+
go func() {
189+
if err := git.GetRepoRawDiffForFile(gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, treePath, writer); err != nil {
190+
_ = writer.CloseWithError(fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", gitRepo.Path, pr.MergeBase, headCommitID, treePath, err))
191+
return
192+
}
193+
_ = writer.Close()
194+
}()
195+
196+
patch, err = git.CutDiffAroundLine(reader, int64((&models.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
197+
if err != nil {
198+
log.Error("Error whilst generating patch: %v", err)
199+
return nil, err
185200
}
186-
patch = git.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines)
187201
}
188202
return models.CreateComment(&models.CreateCommentOptions{
189203
Type: models.CommentTypeCode,

0 commit comments

Comments
 (0)