skipping := skipToFile != "" diff := &Diff{Files: make([]*DiffFile, 0)} sb := strings.Builder{} // OK let's set a reasonable buffer size. // This should be at least the size of maxLineCharacters or 4096 whichever is larger. readerSize := maxLineCharacters if readerSize < 4096 { readerSize = 4096 } input := bufio.NewReaderSize(reader, readerSize) line, err := input.ReadString('\n') if err != nil { if err == io.EOF { return diff, nil } return diff, err } prepareValue := func(s, p string) string { return strings.TrimSpace(strings.TrimPrefix(s, p)) } parsingLoop: for { // 1. A patch file always begins with `diff --git ` + `a/path b/path` (possibly quoted) // if it does not we have bad input! if !strings.HasPrefix(line, cmdDiffHead) { return diff, fmt.Errorf("invalid first file line: %s", line) } if maxFiles > -1 && len(diff.Files) >= maxFiles { lastFile := createDiffFile(diff, line) diff.End = lastFile.Name diff.IsIncomplete = true _, err := io.Copy(io.Discard, reader) if err != nil { // By the definition of io.Copy this never returns io.EOF return diff, fmt.Errorf("error during io.Copy: %w", err) } break parsingLoop } curFile = createDiffFile(diff, line) if skipping { if curFile.Name != skipToFile { line, err = skipToNextDiffHead(input) if err != nil { if err == io.EOF { return diff, nil } return diff, err } continue } skipping = false } diff.Files = append(diff.Files, curFile) // 2. It is followed by one or more extended header lines: // // old mode <mode> // new mode <mode> // deleted file mode <mode> // new file mode <mode> // copy from <path> // copy to <path> // rename from <path> // rename to <path> // similarity index <number> // dissimilarity index <number> // index <hash>..<hash> <mode> // // * <mode> 6-digit octal numbers including the file type and file permission bits. // * <path> does not include the a/ and b/ prefixes // * <number> percentage of unchanged lines for similarity, percentage of changed // lines dissimilarity as integer rounded down with terminal %. 100% => equal files. // * The index line includes the blob object names before and after the change. // The <mode> is included if the file mode does not change; otherwise, separate // lines indicate the old and the new mode. // 3. Following this header the "standard unified" diff format header may be encountered: (but not for every case...) // // --- a/<path> // +++ b/<path> // // With multiple hunks // // @@ <hunk descriptor> @@ // +added line // -removed line // unchanged line // // 4. Binary files get: // // Binary files a/<path> and b/<path> differ // // but one of a/<path> and b/<path> could be /dev/null. curFileLoop: for { line, err = input.ReadString('\n') if err != nil { if err != io.EOF { return diff, err } break parsingLoop } switch { case strings.HasPrefix(line, cmdDiffHead): break curFileLoop case strings.HasPrefix(line, "old mode ") || strings.HasPrefix(line, "new mode "): if strings.HasPrefix(line, "old mode ") { curFile.OldMode = prepareValue(line, "old mode ") } if strings.HasPrefix(line, "new mode ") { curFile.Mode = prepareValue(line, "new mode ") } if strings.HasSuffix(line, " 160000\n") { curFile.IsSubmodule = true } case strings.HasPrefix(line, "rename from "): curFile.IsRenamed = true curFile.Type = DiffFileRename if curFile.IsAmbiguous { curFile.OldName = prepareValue(line, "rename from ") } case strings.HasPrefix(line, "rename to "): curFile.IsRenamed = true curFile.Type = DiffFileRename if curFile.IsAmbiguous { curFile.Name = prepareValue(line, "rename to ") curFile.IsAmbiguous = false } case strings.HasPrefix(line, "copy from "): curFile.IsRenamed = true curFile.Type = DiffFileCopy if curFile.IsAmbiguous { curFile.OldName = prepareValue(line, "copy from ") } case strings.HasPrefix(line, "copy to "): curFile.IsRenamed = true curFile.Type = DiffFileCopy if curFile.IsAmbiguous { curFile.Name = prepareValue(line, "copy to ") curFile.IsAmbiguous = false } case strings.HasPrefix(line, "new file"): curFile.Type = DiffFileAdd curFile.IsCreated = true if strings.HasPrefix(line, "new file mode ") { curFile.Mode = prepareValue(line, "new file mode ") } if strings.HasSuffix(line, " 160000\n") { curFile.IsSubmodule = true } case strings.HasPrefix(line, "deleted"): curFile.Type = DiffFileDel curFile.IsDeleted = true if strings.HasSuffix(line, " 160000\n") { curFile.IsSubmodule = true } case strings.HasPrefix(line, "index"): if strings.HasSuffix(line, " 160000\n") { curFile.IsSubmodule = true } case strings.HasPrefix(line, "similarity index 100%"): curFile.Type = DiffFileRename case strings.HasPrefix(line, "Binary"): curFile.IsBin = true case strings.HasPrefix(line, "--- "): // Handle ambiguous filenames if curFile.IsAmbiguous { // The shortest string that can end up here is: // "--- a\t\n" without the quotes. // This line has a len() of 7 but doesn't contain a oldName. // So the amount that the line need is at least 8 or more. // The code will otherwise panic for a out-of-bounds. if len(line) > 7 && line[4] == 'a' { curFile.OldName = line[6 : len(line)-1] if line[len(line)-2] == '\t' { curFile.OldName = curFile.OldName[:len(curFile.OldName)-1] } } else { curFile.OldName = "" } } // Otherwise do nothing with this line case strings.HasPrefix(line, "+++ "): // Handle ambiguous filenames if curFile.IsAmbiguous { if len(line) > 6 && line[4] == 'b' { curFile.Name = line[6 : len(line)-1] if line[len(line)-2] == '\t' { curFile.Name = curFile.Name[:len(curFile.Name)-1] } if curFile.OldName == "" { curFile.OldName = curFile.Name } } else { curFile.Name = curFile.OldName } curFile.IsAmbiguous = false } // Otherwise do nothing with this line, but now switch to parsing hunks lineBytes, isFragment, err := parseHunks(ctx, curFile, maxLines, maxLineCharacters, input) diff.TotalAddition += curFile.Addition diff.TotalDeletion += curFile.Deletion if err != nil { if err != io.EOF { return diff, err } break parsingLoop } sb.Reset() _, _ = sb.Write(lineBytes) for isFragment { lineBytes, isFragment, err = input.ReadLine() if err != nil { // Now by the definition of ReadLine this cannot be io.EOF return diff, fmt.Errorf("unable to ReadLine: %w", err) } _, _ = sb.Write(lineBytes) } line = sb.String() sb.Reset() break curFileLoop } } } // TODO: There are numerous issues with this: // - we might want to consider detecting encoding while parsing but... // - we're likely to fail to get the correct encoding here anyway as we won't have enough information diffLineTypeBuffers := make(map[DiffLineType]*bytes.Buffer, 3) diffLineTypeDecoders := make(map[DiffLineType]*encoding.Decoder, 3) diffLineTypeBuffers[DiffLinePlain] = new(bytes.Buffer) diffLineTypeBuffers[DiffLineAdd] = new(bytes.Buffer) diffLineTypeBuffers[DiffLineDel] = new(bytes.Buffer) for _, f := range diff.Files { f.NameHash = base.EncodeSha1(f.Name) for _, buffer := range diffLineTypeBuffers { buffer.Reset() } for _, sec := range f.Sections { for _, l := range sec.Lines { if l.Type == DiffLineSection { continue } diffLineTypeBuffers[l.Type].WriteString(l.Content[1:]) diffLineTypeBuffers[l.Type].WriteString("\n") } } for lineType, buffer := range diffLineTypeBuffers { diffLineTypeDecoders[lineType] = nil if buffer.Len() == 0 { continue } charsetLabel, err := charset.DetectEncoding(buffer.Bytes()) if charsetLabel != "UTF-8" && err == nil { encoding, _ := stdcharset.Lookup(charsetLabel) if encoding != nil { diffLineTypeDecoders[lineType] = encoding.NewDecoder() } } } for _, sec := range f.Sections { for _, l := range sec.Lines { decoder := diffLineTypeDecoders[l.Type] if decoder != nil { if c, _, err := transform.String(decoder, l.Content[1:]); err == nil { l.Content = l.Content[0:1] + c } } } } } diff.NumFiles = len(diff.Files) return diff, nil} func skipToNextDiffHead(input *bufio.Reader) (line string, err error) { // need to skip until the next cmdDiffHead var isFragment, wasFragment bool var lineBytes []byte for { lineBytes, isFragment, err = input.ReadLine() if err != nil { return "", err } if wasFragment { wasFragment = isFragment continue } if bytes.HasPrefix(lineBytes, []byte(cmdDiffHead)) { break } wasFragment = isFragment } line = string(lineBytes) if isFragment { var tail string tail, err = input.ReadString('\n') if err != nil { return "", err } line += tail } return line, err} func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio.Reader) (lineBytes []byte, isFragment bool, err error) { sb := strings.Builder{} var ( curSection *DiffSection curFileLinesCount int curFileLFSPrefix bool ) lastLeftIdx := -1 leftLine, rightLine := 1, 1 for { for isFragment { curFile.IsIncomplete = true curFile.IsIncompleteLineTooLong = true _, isFragment, err = input.ReadLine() if err != nil { // Now by the definition of ReadLine this cannot be io.EOF return nil, false, fmt.Errorf("unable to ReadLine: %w", err) } } sb.Reset() lineBytes, isFragment, err = input.ReadLine() if err != nil { if err == io.EOF { return lineBytes, isFragment, err } err = fmt.Errorf("unable to ReadLine: %w", err) return nil, false, err } if lineBytes[0] == 'd' { // End of hunks return lineBytes, isFragment, err } switch lineBytes[0] { case '@': if maxLines > -1 && curFileLinesCount >= maxLines { curFile.IsIncomplete = true continue } _, _ = sb.Write(lineBytes) for isFragment { // This is very odd indeed - we're in a section header and the line is too long // This really shouldn't happen... lineBytes, isFragment, err = input.ReadLine() if err != nil { // Now by the definition of ReadLine this cannot be io.EOF return nil, false, fmt.Errorf("unable to ReadLine: %w", err) } _, _ = sb.Write(lineBytes) } line := sb.String() // Create a new section to represent this hunk curSection = &DiffSection{file: curFile} lastLeftIdx = -1 curFile.Sections = append(curFile.Sections, curSection) lineSectionInfo := getDiffLineSectionInfo(curFile.Name, line, leftLine-1, rightLine-1) diffLine := &DiffLine{ Type: DiffLineSection, Content: line, SectionInfo: lineSectionInfo, } curSection.Lines = append(curSection.Lines, diffLine) curSection.FileName = curFile.Name // update line number. leftLine = lineSectionInfo.LeftIdx rightLine = lineSectionInfo.RightIdx continue case '\\': if maxLines > -1 && curFileLinesCount >= maxLines { curFile.IsIncomplete = true continue } // This is used only to indicate that the current file does not have a terminal newline if !bytes.Equal(lineBytes, []byte("\\ No newline at end of file")) { return nil, false, fmt.Errorf("unexpected line in hunk: %s", string(lineBytes)) } // Technically this should be the end the file! // FIXME: we should be putting a marker at the end of the file if there is no terminal new line continue case '+': curFileLinesCount++ curFile.Addition++ if maxLines > -1 && curFileLinesCount >= maxLines { curFile.IsIncomplete = true continue } diffLine := &DiffLine{Type: DiffLineAdd, RightIdx: rightLine, Match: -1} rightLine++ if curSection == nil { // Create a new section to represent this hunk curSection = &DiffSection{file: curFile} curFile.Sections = append(curFile.Sections, curSection) lastLeftIdx = -1 } if lastLeftIdx > -1 { diffLine.Match = lastLeftIdx curSection.Lines[lastLeftIdx].Match = len(curSection.Lines) lastLeftIdx++ if lastLeftIdx >= len(curSection.Lines) || curSection.Lines[lastLeftIdx].Type != DiffLineDel { lastLeftIdx = -1 } } curSection.Lines = append(curSection.Lines, diffLine) case '-': curFileLinesCount++ curFile.Deletion++ if maxLines > -1 && curFileLinesCount >= maxLines { curFile.IsIncomplete = true continue } diffLine := &DiffLine{Type: DiffLineDel, LeftIdx: leftLine, Match: -1} if leftLine > 0 { leftLine++ } if curSection == nil { // Create a new section to represent this hunk curSection = &DiffSection{file: curFile} curFile.Sections = append(curFile.Sections, curSection) lastLeftIdx = -1 } if len(curSection.Lines) == 0 || curSection.Lines[len(curSection.Lines)-1].Type != DiffLineDel { lastLeftIdx = len(curSection.Lines) } curSection.Lines = append(curSection.Lines, diffLine) case ' ': curFileLinesCount++ if maxLines > -1 && curFileLinesCount >= maxLines { curFile.IsIncomplete = true continue } diffLine := &DiffLine{Type: DiffLinePlain, LeftIdx: leftLine, RightIdx: rightLine} leftLine++ rightLine++ lastLeftIdx = -1 if curSection == nil { // Create a new section to represent this hunk curSection = &DiffSection{file: curFile} curFile.Sections = append(curFile.Sections, curSection) } curSection.Lines = append(curSection.Lines, diffLine) default: // This is unexpected return nil, false, fmt.Errorf("unexpected line in hunk: %s", string(lineBytes)) } line := string(lineBytes) if isFragment { curFile.IsIncomplete = true curFile.IsIncompleteLineTooLong = true for isFragment { lineBytes, isFragment, err = input.ReadLine() if err != nil { // Now by the definition of ReadLine this cannot be io.EOF return lineBytes, isFragment, fmt.Errorf("unable to ReadLine: %w", err) } } } if len(line) > maxLineCharacters { curFile.IsIncomplete = true curFile.IsIncompleteLineTooLong = true line = line[:maxLineCharacters] } curSection.Lines[len(curSection.Lines)-1].Content = line // handle LFS if line[1:] == lfs.MetaFileIdentifier { curFileLFSPrefix = true } else if curFileLFSPrefix && strings.HasPrefix(line[1:], lfs.MetaFileOidPrefix) { oid := strings.TrimPrefix(line[1:], lfs.MetaFileOidPrefix) if len(oid) == 64 { m := &git_model.LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}} count, err := db.CountByBean(ctx, m) if err == nil && count > 0 {Lines 501–1000 of 1406. Previous lines Load more
skipping := skipToFile != "" diff := &Diff{Files: make([]*DiffFile, 0)} sb := strings.Builder{} // OK let's set a reasonable buffer size. // This should be at least the size of maxLineCharacters or 4096 whichever is larger. readerSize := maxLineCharacters if readerSize < 4096 { readerSize = 4096 } input := bufio.NewReaderSize(reader, readerSize) line, err := input.ReadString('\n') if err != nil { if err == io.EOF { return diff, nil } return diff, err } prepareValue := func(s, p string) string { return strings.TrimSpace(strings.TrimPrefix(s, p)) } parsingLoop: for { // 1. A patch file always begins with `diff --git ` + `a/path b/path` (possibly quoted) // if it does not we have bad input! if !strings.HasPrefix(line, cmdDiffHead) { return diff, fmt.Errorf("invalid first file line: %s", line) } if maxFiles > -1 && len(diff.Files) >= maxFiles { lastFile := createDiffFile(diff, line) diff.End = lastFile.Name diff.IsIncomplete = true _, err := io.Copy(io.Discard, reader) if err != nil { // By the definition of io.Copy this never returns io.EOF return diff, fmt.Errorf("error during io.Copy: %w", err) } break parsingLoop } curFile = createDiffFile(diff, line) if skipping { if curFile.Name != skipToFile { line, err = skipToNextDiffHead(input) if err != nil { if err == io.EOF { return diff, nil } return diff, err } continue } skipping = false } diff.Files = append(diff.Files, curFile) // 2. It is followed by one or more extended header lines: // // old mode <mode> // new mode <mode> // deleted file mode <mode> // new file mode <mode> // copy from <path> // copy to <path> // rename from <path> // rename to <path> // similarity index <number> // dissimilarity index <number> // index <hash>..<hash> <mode> // // * <mode> 6-digit octal numbers including the file type and file permission bits. // * <path> does not include the a/ and b/ prefixes // * <number> percentage of unchanged lines for similarity, percentage of changed // lines dissimilarity as integer rounded down with terminal %. 100% => equal files. // * The index line includes the blob object names before and after the change. // The <mode> is included if the file mode does not change; otherwise, separate // lines indicate the old and the new mode. // 3. Following this header the "standard unified" diff format header may be encountered: (but not for every case...) // // --- a/<path> // +++ b/<path> // // With multiple hunks // // @@ <hunk descriptor> @@ // +added line // -removed line // unchanged line // // 4. Binary files get: // // Binary files a/<path> and b/<path> differ // // but one of a/<path> and b/<path> could be /dev/null. curFileLoop: for { line, err = input.ReadString('\n') if err != nil { if err != io.EOF { return diff, err } break parsingLoop } switch { case strings.HasPrefix(line, cmdDiffHead): break curFileLoop case strings.HasPrefix(line, "old mode ") || strings.HasPrefix(line, "new mode "): if strings.HasPrefix(line, "old mode ") { curFile.OldMode = prepareValue(line, "old mode ") } if strings.HasPrefix(line, "new mode ") { curFile.Mode = prepareValue(line, "new mode ") } if strings.HasSuffix(line, " 160000\n") { curFile.IsSubmodule = true } case strings.HasPrefix(line, "rename from "): curFile.IsRenamed = true curFile.Type = DiffFileRename if curFile.IsAmbiguous { curFile.OldName = prepareValue(line, "rename from ") } case strings.HasPrefix(line, "rename to "): curFile.IsRenamed = true curFile.Type = DiffFileRename if curFile.IsAmbiguous { curFile.Name = prepareValue(line, "rename to ") curFile.IsAmbiguous = false } case strings.HasPrefix(line, "copy from "): curFile.IsRenamed = true curFile.Type = DiffFileCopy if curFile.IsAmbiguous { curFile.OldName = prepareValue(line, "copy from ") } case strings.HasPrefix(line, "copy to "): curFile.IsRenamed = true curFile.Type = DiffFileCopy if curFile.IsAmbiguous { curFile.Name = prepareValue(line, "copy to ") curFile.IsAmbiguous = false } case strings.HasPrefix(line, "new file"): curFile.Type = DiffFileAdd curFile.IsCreated = true if strings.HasPrefix(line, "new file mode ") { curFile.Mode = prepareValue(line, "new file mode ") } if strings.HasSuffix(line, " 160000\n") { curFile.IsSubmodule = true } case strings.HasPrefix(line, "deleted"): curFile.Type = DiffFileDel curFile.IsDeleted = true if strings.HasSuffix(line, " 160000\n") { curFile.IsSubmodule = true } case strings.HasPrefix(line, "index"): if strings.HasSuffix(line, " 160000\n") { curFile.IsSubmodule = true } case strings.HasPrefix(line, "similarity index 100%"): curFile.Type = DiffFileRename case strings.HasPrefix(line, "Binary"): curFile.IsBin = true case strings.HasPrefix(line, "--- "): // Handle ambiguous filenames if curFile.IsAmbiguous { // The shortest string that can end up here is: // "--- a\t\n" without the quotes. // This line has a len() of 7 but doesn't contain a oldName. // So the amount that the line need is at least 8 or more. // The code will otherwise panic for a out-of-bounds. if len(line) > 7 && line[4] == 'a' { curFile.OldName = line[6 : len(line)-1] if line[len(line)-2] == '\t' { curFile.OldName = curFile.OldName[:len(curFile.OldName)-1] } } else { curFile.OldName = "" } } // Otherwise do nothing with this line case strings.HasPrefix(line, "+++ "): // Handle ambiguous filenames if curFile.IsAmbiguous { if len(line) > 6 && line[4] == 'b' { curFile.Name = line[6 : len(line)-1] if line[len(line)-2] == '\t' { curFile.Name = curFile.Name[:len(curFile.Name)-1] } if curFile.OldName == "" { curFile.OldName = curFile.Name } } else { curFile.Name = curFile.OldName } curFile.IsAmbiguous = false } // Otherwise do nothing with this line, but now switch to parsing hunks lineBytes, isFragment, err := parseHunks(ctx, curFile, maxLines, maxLineCharacters, input) diff.TotalAddition += curFile.Addition diff.TotalDeletion += curFile.Deletion if err != nil { if err != io.EOF { return diff, err } break parsingLoop } sb.Reset() _, _ = sb.Write(lineBytes) for isFragment { lineBytes, isFragment, err = input.ReadLine() if err != nil { // Now by the definition of ReadLine this cannot be io.EOF return diff, fmt.Errorf("unable to ReadLine: %w", err) } _, _ = sb.Write(lineBytes) } line = sb.String() sb.Reset() break curFileLoop } } } // TODO: There are numerous issues with this: // - we might want to consider detecting encoding while parsing but... // - we're likely to fail to get the correct encoding here anyway as we won't have enough information diffLineTypeBuffers := make(map[DiffLineType]*bytes.Buffer, 3) diffLineTypeDecoders := make(map[DiffLineType]*encoding.Decoder, 3) diffLineTypeBuffers[DiffLinePlain] = new(bytes.Buffer) diffLineTypeBuffers[DiffLineAdd] = new(bytes.Buffer) diffLineTypeBuffers[DiffLineDel] = new(bytes.Buffer) for _, f := range diff.Files { f.NameHash = base.EncodeSha1(f.Name) for _, buffer := range diffLineTypeBuffers { buffer.Reset() } for _, sec := range f.Sections { for _, l := range sec.Lines { if l.Type == DiffLineSection { continue } diffLineTypeBuffers[l.Type].WriteString(l.Content[1:]) diffLineTypeBuffers[l.Type].WriteString("\n") } } for lineType, buffer := range diffLineTypeBuffers { diffLineTypeDecoders[lineType] = nil if buffer.Len() == 0 { continue } charsetLabel, err := charset.DetectEncoding(buffer.Bytes()) if charsetLabel != "UTF-8" && err == nil { encoding, _ := stdcharset.Lookup(charsetLabel) if encoding != nil { diffLineTypeDecoders[lineType] = encoding.NewDecoder() } } } for _, sec := range f.Sections { for _, l := range sec.Lines { decoder := diffLineTypeDecoders[l.Type] if decoder != nil { if c, _, err := transform.String(decoder, l.Content[1:]); err == nil { l.Content = l.Content[0:1] + c } } } } } diff.NumFiles = len(diff.Files) return diff, nil } func skipToNextDiffHead(input *bufio.Reader) (line string, err error) { // need to skip until the next cmdDiffHead var isFragment, wasFragment bool var lineBytes []byte for { lineBytes, isFragment, err = input.ReadLine() if err != nil { return "", err } if wasFragment { wasFragment = isFragment continue } if bytes.HasPrefix(lineBytes, []byte(cmdDiffHead)) { break } wasFragment = isFragment } line = string(lineBytes) if isFragment { var tail string tail, err = input.ReadString('\n') if err != nil { return "", err } line += tail } return line, err } func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio.Reader) (lineBytes []byte, isFragment bool, err error) { sb := strings.Builder{} var ( curSection *DiffSection curFileLinesCount int curFileLFSPrefix bool ) lastLeftIdx := -1 leftLine, rightLine := 1, 1 for { for isFragment { curFile.IsIncomplete = true curFile.IsIncompleteLineTooLong = true _, isFragment, err = input.ReadLine() if err != nil { // Now by the definition of ReadLine this cannot be io.EOF return nil, false, fmt.Errorf("unable to ReadLine: %w", err) } } sb.Reset() lineBytes, isFragment, err = input.ReadLine() if err != nil { if err == io.EOF { return lineBytes, isFragment, err } err = fmt.Errorf("unable to ReadLine: %w", err) return nil, false, err } if lineBytes[0] == 'd' { // End of hunks return lineBytes, isFragment, err } switch lineBytes[0] { case '@': if maxLines > -1 && curFileLinesCount >= maxLines { curFile.IsIncomplete = true continue } _, _ = sb.Write(lineBytes) for isFragment { // This is very odd indeed - we're in a section header and the line is too long // This really shouldn't happen... lineBytes, isFragment, err = input.ReadLine() if err != nil { // Now by the definition of ReadLine this cannot be io.EOF return nil, false, fmt.Errorf("unable to ReadLine: %w", err) } _, _ = sb.Write(lineBytes) } line := sb.String() // Create a new section to represent this hunk curSection = &DiffSection{file: curFile} lastLeftIdx = -1 curFile.Sections = append(curFile.Sections, curSection) lineSectionInfo := getDiffLineSectionInfo(curFile.Name, line, leftLine-1, rightLine-1) diffLine := &DiffLine{ Type: DiffLineSection, Content: line, SectionInfo: lineSectionInfo, } curSection.Lines = append(curSection.Lines, diffLine) curSection.FileName = curFile.Name // update line number. leftLine = lineSectionInfo.LeftIdx rightLine = lineSectionInfo.RightIdx continue case '\\': if maxLines > -1 && curFileLinesCount >= maxLines { curFile.IsIncomplete = true continue } // This is used only to indicate that the current file does not have a terminal newline if !bytes.Equal(lineBytes, []byte("\\ No newline at end of file")) { return nil, false, fmt.Errorf("unexpected line in hunk: %s", string(lineBytes)) } // Technically this should be the end the file! // FIXME: we should be putting a marker at the end of the file if there is no terminal new line continue case '+': curFileLinesCount++ curFile.Addition++ if maxLines > -1 && curFileLinesCount >= maxLines { curFile.IsIncomplete = true continue } diffLine := &DiffLine{Type: DiffLineAdd, RightIdx: rightLine, Match: -1} rightLine++ if curSection == nil { // Create a new section to represent this hunk curSection = &DiffSection{file: curFile} curFile.Sections = append(curFile.Sections, curSection) lastLeftIdx = -1 } if lastLeftIdx > -1 { diffLine.Match = lastLeftIdx curSection.Lines[lastLeftIdx].Match = len(curSection.Lines) lastLeftIdx++ if lastLeftIdx >= len(curSection.Lines) || curSection.Lines[lastLeftIdx].Type != DiffLineDel { lastLeftIdx = -1 } } curSection.Lines = append(curSection.Lines, diffLine) case '-': curFileLinesCount++ curFile.Deletion++ if maxLines > -1 && curFileLinesCount >= maxLines { curFile.IsIncomplete = true continue } diffLine := &DiffLine{Type: DiffLineDel, LeftIdx: leftLine, Match: -1} if leftLine > 0 { leftLine++ } if curSection == nil { // Create a new section to represent this hunk curSection = &DiffSection{file: curFile} curFile.Sections = append(curFile.Sections, curSection) lastLeftIdx = -1 } if len(curSection.Lines) == 0 || curSection.Lines[len(curSection.Lines)-1].Type != DiffLineDel { lastLeftIdx = len(curSection.Lines) } curSection.Lines = append(curSection.Lines, diffLine) case ' ': curFileLinesCount++ if maxLines > -1 && curFileLinesCount >= maxLines { curFile.IsIncomplete = true continue } diffLine := &DiffLine{Type: DiffLinePlain, LeftIdx: leftLine, RightIdx: rightLine} leftLine++ rightLine++ lastLeftIdx = -1 if curSection == nil { // Create a new section to represent this hunk curSection = &DiffSection{file: curFile} curFile.Sections = append(curFile.Sections, curSection) } curSection.Lines = append(curSection.Lines, diffLine) default: // This is unexpected return nil, false, fmt.Errorf("unexpected line in hunk: %s", string(lineBytes)) } line := string(lineBytes) if isFragment { curFile.IsIncomplete = true curFile.IsIncompleteLineTooLong = true for isFragment { lineBytes, isFragment, err = input.ReadLine() if err != nil { // Now by the definition of ReadLine this cannot be io.EOF return lineBytes, isFragment, fmt.Errorf("unable to ReadLine: %w", err) } } } if len(line) > maxLineCharacters { curFile.IsIncomplete = true curFile.IsIncompleteLineTooLong = true line = line[:maxLineCharacters] } curSection.Lines[len(curSection.Lines)-1].Content = line // handle LFS if line[1:] == lfs.MetaFileIdentifier { curFileLFSPrefix = true } else if curFileLFSPrefix && strings.HasPrefix(line[1:], lfs.MetaFileOidPrefix) { oid := strings.TrimPrefix(line[1:], lfs.MetaFileOidPrefix) if len(oid) == 64 { m := &git_model.LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}} count, err := db.CountByBean(ctx, m) if err == nil && count > 0 {