mirror of
https://github.com/go-gitea/gitea.git
synced 2026-02-23 23:43:43 +01:00
- Merge ExcerptBlob and excerptBlobBatch into a single unified flow that handles both single and batch requests without duplicated code - Extract parseBatchBlobExcerptOptions to DRY the repetitive splitInts calls - Refactor BuildBlobExcerptDiffSection to separate section building from highlighting, enabling BuildBlobExcerptDiffSections (plural) to highlight the file content only once for all sections instead of N times - Add blob size limit check (MaxDisplayFileSize) before io.ReadAll to prevent OOM on large files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
161 lines
5.0 KiB
Go
161 lines
5.0 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package gitdiff
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
|
|
"code.gitea.io/gitea/modules/setting"
|
|
|
|
"github.com/alecthomas/chroma/v2"
|
|
)
|
|
|
|
type BlobExcerptOptions struct {
|
|
LastLeft int
|
|
LastRight int
|
|
LeftIndex int
|
|
RightIndex int
|
|
LeftHunkSize int
|
|
RightHunkSize int
|
|
Direction string
|
|
Language string
|
|
}
|
|
|
|
// fillExcerptLines reads from reader and populates section.Lines.
|
|
// It returns the accumulated content buffer for later highlighting.
|
|
func fillExcerptLines(section *DiffSection, reader io.Reader, idxLeft, idxRight, chunkSize int) ([]byte, error) {
|
|
buf := &bytes.Buffer{}
|
|
scanner := bufio.NewScanner(reader)
|
|
var diffLines []*DiffLine
|
|
for line := 0; line < idxRight+chunkSize; line++ {
|
|
if ok := scanner.Scan(); !ok {
|
|
break
|
|
}
|
|
lineText := scanner.Text()
|
|
if buf.Len()+len(lineText) < int(setting.UI.MaxDisplayFileSize) {
|
|
buf.WriteString(lineText)
|
|
buf.WriteByte('\n')
|
|
}
|
|
if line < idxRight {
|
|
continue
|
|
}
|
|
diffLine := &DiffLine{
|
|
LeftIdx: idxLeft + (line - idxRight) + 1,
|
|
RightIdx: line + 1,
|
|
Type: DiffLinePlain,
|
|
Content: " " + lineText,
|
|
}
|
|
diffLines = append(diffLines, diffLine)
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, fmt.Errorf("fillExcerptLines scan: %w", err)
|
|
}
|
|
section.Lines = diffLines
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// buildExcerptDiffSection builds a single excerpt section without highlighting.
|
|
// It returns the section and the accumulated content buffer.
|
|
func buildExcerptDiffSection(filePath string, reader io.Reader, opts BlobExcerptOptions) (*DiffSection, []byte, error) {
|
|
lastLeft, lastRight, idxLeft, idxRight := opts.LastLeft, opts.LastRight, opts.LeftIndex, opts.RightIndex
|
|
leftHunkSize, rightHunkSize, direction := opts.LeftHunkSize, opts.RightHunkSize, opts.Direction
|
|
|
|
chunkSize := BlobExcerptChunkSize
|
|
section := &DiffSection{
|
|
language: &diffVarMutable[string]{value: opts.Language},
|
|
highlightLexer: &diffVarMutable[chroma.Lexer]{},
|
|
highlightedLeftLines: &diffVarMutable[map[int]template.HTML]{},
|
|
highlightedRightLines: &diffVarMutable[map[int]template.HTML]{},
|
|
FileName: filePath,
|
|
}
|
|
var bufContent []byte
|
|
var err error
|
|
if direction == "up" && (idxLeft-lastLeft) > chunkSize {
|
|
idxLeft -= chunkSize
|
|
idxRight -= chunkSize
|
|
leftHunkSize += chunkSize
|
|
rightHunkSize += chunkSize
|
|
bufContent, err = fillExcerptLines(section, reader, idxLeft-1, idxRight-1, chunkSize)
|
|
} else if direction == "down" && (idxLeft-lastLeft) > chunkSize {
|
|
bufContent, err = fillExcerptLines(section, reader, lastLeft, lastRight, chunkSize)
|
|
lastLeft += chunkSize
|
|
lastRight += chunkSize
|
|
} else {
|
|
offset := -1
|
|
if direction == "down" {
|
|
offset = 0
|
|
}
|
|
bufContent, err = fillExcerptLines(section, reader, lastLeft, lastRight, idxRight-lastRight+offset)
|
|
leftHunkSize = 0
|
|
rightHunkSize = 0
|
|
idxLeft = lastLeft
|
|
idxRight = lastRight
|
|
}
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
newLineSection := &DiffLine{
|
|
Type: DiffLineSection,
|
|
SectionInfo: &DiffLineSectionInfo{
|
|
language: &diffVarMutable[string]{value: opts.Language},
|
|
Path: filePath,
|
|
LastLeftIdx: lastLeft,
|
|
LastRightIdx: lastRight,
|
|
LeftIdx: idxLeft,
|
|
RightIdx: idxRight,
|
|
LeftHunkSize: leftHunkSize,
|
|
RightHunkSize: rightHunkSize,
|
|
},
|
|
}
|
|
if newLineSection.GetExpandDirection() != "" {
|
|
newLineSection.Content = fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", idxLeft, leftHunkSize, idxRight, rightHunkSize)
|
|
switch direction {
|
|
case "up":
|
|
section.Lines = append([]*DiffLine{newLineSection}, section.Lines...)
|
|
case "down":
|
|
section.Lines = append(section.Lines, newLineSection)
|
|
}
|
|
}
|
|
return section, bufContent, nil
|
|
}
|
|
|
|
// BuildBlobExcerptDiffSection builds a single excerpt section with highlighting.
|
|
func BuildBlobExcerptDiffSection(filePath string, reader io.Reader, opts BlobExcerptOptions) (*DiffSection, error) {
|
|
section, bufContent, err := buildExcerptDiffSection(filePath, reader, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// DiffLinePlain always uses right lines
|
|
section.highlightedRightLines.value = highlightCodeLines(filePath, opts.Language, []*DiffSection{section}, false /* right */, bufContent)
|
|
return section, nil
|
|
}
|
|
|
|
// BuildBlobExcerptDiffSections builds multiple excerpt sections from the same file content,
|
|
// highlighting the content only once for all sections.
|
|
func BuildBlobExcerptDiffSections(filePath string, content []byte, optsList []BlobExcerptOptions) ([]*DiffSection, error) {
|
|
sections := make([]*DiffSection, len(optsList))
|
|
for i, opts := range optsList {
|
|
section, _, err := buildExcerptDiffSection(filePath, bytes.NewReader(content), opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sections[i] = section
|
|
}
|
|
|
|
// Highlight once for all sections
|
|
if len(optsList) > 0 {
|
|
highlighted := highlightCodeLines(filePath, optsList[0].Language, sections, false /* right */, content)
|
|
for _, section := range sections {
|
|
section.highlightedRightLines.value = highlighted
|
|
}
|
|
}
|
|
|
|
return sections, nil
|
|
}
|