Skip to content

Commit 931d0cf

Browse files
authored
Enable show more files in diff for git <2.31 (#17733)
Unfortunately due to a misread on my behalf I missed that git diff only learned --skip-to in version 2.31.0. Thus this functionality was not working on older versions of git. This PR adds a handler that simply allows for us to skip reading the diffs until we find the correct file to skip to. Fix #17731 Signed-off-by: Andrew Thornton <[email protected]>
1 parent 0d69e64 commit 931d0cf

File tree

4 files changed

+253
-32
lines changed

4 files changed

+253
-32
lines changed

modules/repofiles/temp_repo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) {
302302
if err := git.NewCommand("diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD").
303303
RunInDirTimeoutEnvFullPipelineFunc(nil, 30*time.Second, t.basePath, stdoutWriter, stderr, nil, func(ctx context.Context, cancel context.CancelFunc) error {
304304
_ = stdoutWriter.Close()
305-
diff, finalErr = gitdiff.ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader)
305+
diff, finalErr = gitdiff.ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader, "")
306306
if finalErr != nil {
307307
log.Error("ParsePatch: %v", finalErr)
308308
cancel()

services/gitdiff/csv_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ c,d,e`,
188188
}
189189

190190
for n, c := range cases {
191-
diff, err := ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.diff))
191+
diff, err := ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.diff), "")
192192
if err != nil {
193193
t.Errorf("ParsePatch failed: %s", err)
194194
}

services/gitdiff/gitdiff.go

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -694,9 +694,11 @@ func (diff *Diff) LoadComments(issue *models.Issue, currentUser *models.User) er
694694
const cmdDiffHead = "diff --git "
695695

696696
// ParsePatch builds a Diff object from a io.Reader and some parameters.
697-
func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*Diff, error) {
697+
func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader, skipToFile string) (*Diff, error) {
698698
var curFile *DiffFile
699699

700+
skipping := skipToFile != ""
701+
700702
diff := &Diff{Files: make([]*DiffFile, 0)}
701703

702704
sb := strings.Builder{}
@@ -721,24 +723,36 @@ parsingLoop:
721723
// 1. A patch file always begins with `diff --git ` + `a/path b/path` (possibly quoted)
722724
// if it does not we have bad input!
723725
if !strings.HasPrefix(line, cmdDiffHead) {
724-
return diff, fmt.Errorf("Invalid first file line: %s", line)
726+
return diff, fmt.Errorf("invalid first file line: %s", line)
725727
}
726728

727-
// TODO: Handle skipping first n files
728729
if len(diff.Files) >= maxFiles {
729-
730730
lastFile := createDiffFile(diff, line)
731731
diff.End = lastFile.Name
732732
diff.IsIncomplete = true
733733
_, err := io.Copy(io.Discard, reader)
734734
if err != nil {
735735
// By the definition of io.Copy this never returns io.EOF
736-
return diff, fmt.Errorf("Copy: %v", err)
736+
return diff, fmt.Errorf("error during io.Copy: %w", err)
737737
}
738738
break parsingLoop
739739
}
740740

741741
curFile = createDiffFile(diff, line)
742+
if skipping {
743+
if curFile.Name != skipToFile {
744+
line, err = skipToNextDiffHead(input)
745+
if err != nil {
746+
if err == io.EOF {
747+
return diff, nil
748+
}
749+
return diff, err
750+
}
751+
continue
752+
}
753+
skipping = false
754+
}
755+
742756
diff.Files = append(diff.Files, curFile)
743757

744758
// 2. It is followed by one or more extended header lines:
@@ -892,7 +906,7 @@ parsingLoop:
892906
lineBytes, isFragment, err = input.ReadLine()
893907
if err != nil {
894908
// Now by the definition of ReadLine this cannot be io.EOF
895-
return diff, fmt.Errorf("Unable to ReadLine: %v", err)
909+
return diff, fmt.Errorf("unable to ReadLine: %w", err)
896910
}
897911
_, _ = sb.Write(lineBytes)
898912
}
@@ -955,6 +969,36 @@ parsingLoop:
955969
return diff, nil
956970
}
957971

972+
func skipToNextDiffHead(input *bufio.Reader) (line string, err error) {
973+
// need to skip until the next cmdDiffHead
974+
isFragment, wasFragment := false, false
975+
var lineBytes []byte
976+
for {
977+
lineBytes, isFragment, err = input.ReadLine()
978+
if err != nil {
979+
return
980+
}
981+
if wasFragment {
982+
wasFragment = isFragment
983+
continue
984+
}
985+
if bytes.HasPrefix(lineBytes, []byte(cmdDiffHead)) {
986+
break
987+
}
988+
wasFragment = isFragment
989+
}
990+
line = string(lineBytes)
991+
if isFragment {
992+
var tail string
993+
tail, err = input.ReadString('\n')
994+
if err != nil {
995+
return
996+
}
997+
line += tail
998+
}
999+
return
1000+
}
1001+
9581002
func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio.Reader) (lineBytes []byte, isFragment bool, err error) {
9591003
sb := strings.Builder{}
9601004

@@ -974,7 +1018,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
9741018
_, isFragment, err = input.ReadLine()
9751019
if err != nil {
9761020
// Now by the definition of ReadLine this cannot be io.EOF
977-
err = fmt.Errorf("Unable to ReadLine: %v", err)
1021+
err = fmt.Errorf("unable to ReadLine: %w", err)
9781022
return
9791023
}
9801024
}
@@ -984,7 +1028,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
9841028
if err == io.EOF {
9851029
return
9861030
}
987-
err = fmt.Errorf("Unable to ReadLine: %v", err)
1031+
err = fmt.Errorf("unable to ReadLine: %w", err)
9881032
return
9891033
}
9901034
if lineBytes[0] == 'd' {
@@ -1006,7 +1050,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
10061050
lineBytes, isFragment, err = input.ReadLine()
10071051
if err != nil {
10081052
// Now by the definition of ReadLine this cannot be io.EOF
1009-
err = fmt.Errorf("Unable to ReadLine: %v", err)
1053+
err = fmt.Errorf("unable to ReadLine: %w", err)
10101054
return
10111055
}
10121056
_, _ = sb.Write(lineBytes)
@@ -1037,7 +1081,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
10371081
}
10381082
// This is used only to indicate that the current file does not have a terminal newline
10391083
if !bytes.Equal(lineBytes, []byte("\\ No newline at end of file")) {
1040-
err = fmt.Errorf("Unexpected line in hunk: %s", string(lineBytes))
1084+
err = fmt.Errorf("unexpected line in hunk: %s", string(lineBytes))
10411085
return
10421086
}
10431087
// Technically this should be the end the file!
@@ -1106,7 +1150,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
11061150
curSection.Lines = append(curSection.Lines, diffLine)
11071151
default:
11081152
// This is unexpected
1109-
err = fmt.Errorf("Unexpected line in hunk: %s", string(lineBytes))
1153+
err = fmt.Errorf("unexpected line in hunk: %s", string(lineBytes))
11101154
return
11111155
}
11121156

@@ -1118,7 +1162,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
11181162
lineBytes, isFragment, err = input.ReadLine()
11191163
if err != nil {
11201164
// Now by the definition of ReadLine this cannot be io.EOF
1121-
err = fmt.Errorf("Unable to ReadLine: %v", err)
1165+
err = fmt.Errorf("unable to ReadLine: %w", err)
11221166
return
11231167
}
11241168
}
@@ -1279,8 +1323,14 @@ func GetDiffRangeWithWhitespaceBehavior(gitRepo *git.Repository, beforeCommitID,
12791323
diffArgs = append(diffArgs, afterCommitID)
12801324
beforeCommitID = actualBeforeCommitID
12811325
}
1282-
if skipTo != "" {
1326+
1327+
// In git 2.31, git diff learned --skip-to which we can use to shortcut skip to file
1328+
// so if we are using at least this version of git we don't have to tell ParsePatch to do
1329+
// the skipping for us
1330+
parsePatchSkipToFile := skipTo
1331+
if skipTo != "" && git.CheckGitVersionAtLeast("2.31") == nil {
12831332
diffArgs = append(diffArgs, "--skip-to="+skipTo)
1333+
parsePatchSkipToFile = ""
12841334
}
12851335
cmd := exec.CommandContext(ctx, git.GitExecutable, diffArgs...)
12861336

@@ -1289,19 +1339,19 @@ func GetDiffRangeWithWhitespaceBehavior(gitRepo *git.Repository, beforeCommitID,
12891339

12901340
stdout, err := cmd.StdoutPipe()
12911341
if err != nil {
1292-
return nil, fmt.Errorf("StdoutPipe: %v", err)
1342+
return nil, fmt.Errorf("error creating StdoutPipe: %w", err)
12931343
}
12941344

12951345
if err = cmd.Start(); err != nil {
1296-
return nil, fmt.Errorf("Start: %v", err)
1346+
return nil, fmt.Errorf("error during Start: %w", err)
12971347
}
12981348

12991349
pid := process.GetManager().Add(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath), cancel)
13001350
defer process.GetManager().Remove(pid)
13011351

1302-
diff, err := ParsePatch(maxLines, maxLineCharacters, maxFiles, stdout)
1352+
diff, err := ParsePatch(maxLines, maxLineCharacters, maxFiles, stdout, parsePatchSkipToFile)
13031353
if err != nil {
1304-
return nil, fmt.Errorf("ParsePatch: %v", err)
1354+
return nil, fmt.Errorf("unable to ParsePatch: %w", err)
13051355
}
13061356
diff.Start = skipTo
13071357

@@ -1383,7 +1433,7 @@ func GetDiffRangeWithWhitespaceBehavior(gitRepo *git.Repository, beforeCommitID,
13831433
}
13841434

13851435
if err = cmd.Wait(); err != nil {
1386-
return nil, fmt.Errorf("Wait: %v", err)
1436+
return nil, fmt.Errorf("error during cmd.Wait: %w", err)
13871437
}
13881438

13891439
separator := "..."
@@ -1418,7 +1468,7 @@ func GetDiffCommitWithWhitespaceBehavior(gitRepo *git.Repository, commitID, skip
14181468
// CommentAsDiff returns c.Patch as *Diff
14191469
func CommentAsDiff(c *models.Comment) (*Diff, error) {
14201470
diff, err := ParsePatch(setting.Git.MaxGitDiffLines,
1421-
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.Patch))
1471+
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.Patch), "")
14221472
if err != nil {
14231473
log.Error("Unable to parse patch: %v", err)
14241474
return nil, err

0 commit comments

Comments
 (0)