mirror of
https://gitcode.com/gitea/gitea.git
synced 2025-05-25 19:17:19 +08:00
Full-file syntax highlighting for diff pages (#33766)
Fix #33358, fix #21970 This adds a step in the `GitDiffForRender` that does syntax highlighting for the entire file and then only references lines from that syntax highlighted code. This allows things like multi-line comments to be syntax highlighted correctly. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@ -31,6 +31,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
stdcharset "golang.org/x/net/html/charset"
|
||||
@ -75,12 +76,12 @@ const (
|
||||
|
||||
// DiffLine represents a line difference in a DiffSection.
|
||||
type DiffLine struct {
|
||||
LeftIdx int
|
||||
RightIdx int
|
||||
Match int
|
||||
LeftIdx int // line number, 1-based
|
||||
RightIdx int // line number, 1-based
|
||||
Match int // line number, 1-based
|
||||
Type DiffLineType
|
||||
Content string
|
||||
Comments issues_model.CommentList
|
||||
Comments issues_model.CommentList // related PR code comments
|
||||
SectionInfo *DiffLineSectionInfo
|
||||
}
|
||||
|
||||
@ -95,9 +96,18 @@ type DiffLineSectionInfo struct {
|
||||
RightHunkSize int
|
||||
}
|
||||
|
||||
// DiffHTMLOperation is the HTML version of diffmatchpatch.Diff
|
||||
type DiffHTMLOperation struct {
|
||||
Type diffmatchpatch.Operation
|
||||
HTML template.HTML
|
||||
}
|
||||
|
||||
// BlobExcerptChunkSize represent max lines of excerpt
|
||||
const BlobExcerptChunkSize = 20
|
||||
|
||||
// MaxDiffHighlightEntireFileSize is the maximum file size that will be highlighted with "entire file diff"
|
||||
const MaxDiffHighlightEntireFileSize = 1 * 1024 * 1024
|
||||
|
||||
// GetType returns the type of DiffLine.
|
||||
func (d *DiffLine) GetType() int {
|
||||
return int(d.Type)
|
||||
@ -112,8 +122,9 @@ func (d *DiffLine) GetHTMLDiffLineType() string {
|
||||
return "del"
|
||||
case DiffLineSection:
|
||||
return "tag"
|
||||
default:
|
||||
return "same"
|
||||
}
|
||||
return "same"
|
||||
}
|
||||
|
||||
// CanComment returns whether a line can get commented
|
||||
@ -196,38 +207,6 @@ type DiffSection struct {
|
||||
Lines []*DiffLine
|
||||
}
|
||||
|
||||
var (
|
||||
addedCodePrefix = []byte(`<span class="added-code">`)
|
||||
removedCodePrefix = []byte(`<span class="removed-code">`)
|
||||
codeTagSuffix = []byte(`</span>`)
|
||||
)
|
||||
|
||||
func diffToHTML(lineWrapperTags []string, diffs []diffmatchpatch.Diff, lineType DiffLineType) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
// restore the line wrapper tags <span class="line"> and <span class="cl">, if necessary
|
||||
for _, tag := range lineWrapperTags {
|
||||
buf.WriteString(tag)
|
||||
}
|
||||
for _, diff := range diffs {
|
||||
switch {
|
||||
case diff.Type == diffmatchpatch.DiffEqual:
|
||||
buf.WriteString(diff.Text)
|
||||
case diff.Type == diffmatchpatch.DiffInsert && lineType == DiffLineAdd:
|
||||
buf.Write(addedCodePrefix)
|
||||
buf.WriteString(diff.Text)
|
||||
buf.Write(codeTagSuffix)
|
||||
case diff.Type == diffmatchpatch.DiffDelete && lineType == DiffLineDel:
|
||||
buf.Write(removedCodePrefix)
|
||||
buf.WriteString(diff.Text)
|
||||
buf.Write(codeTagSuffix)
|
||||
}
|
||||
}
|
||||
for range lineWrapperTags {
|
||||
buf.WriteString("</span>")
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// GetLine gets a specific line by type (add or del) and file line number
|
||||
func (diffSection *DiffSection) GetLine(lineType DiffLineType, idx int) *DiffLine {
|
||||
var (
|
||||
@ -271,10 +250,10 @@ LOOP:
|
||||
return nil
|
||||
}
|
||||
|
||||
var diffMatchPatch = diffmatchpatch.New()
|
||||
|
||||
func init() {
|
||||
diffMatchPatch.DiffEditCost = 100
|
||||
func defaultDiffMatchPatch() *diffmatchpatch.DiffMatchPatch {
|
||||
dmp := diffmatchpatch.New()
|
||||
dmp.DiffEditCost = 100
|
||||
return dmp
|
||||
}
|
||||
|
||||
// DiffInline is a struct that has a content and escape status
|
||||
@ -283,97 +262,114 @@ type DiffInline struct {
|
||||
Content template.HTML
|
||||
}
|
||||
|
||||
// DiffInlineWithUnicodeEscape makes a DiffInline with hidden unicode characters escaped
|
||||
// DiffInlineWithUnicodeEscape makes a DiffInline with hidden Unicode characters escaped
|
||||
func DiffInlineWithUnicodeEscape(s template.HTML, locale translation.Locale) DiffInline {
|
||||
status, content := charset.EscapeControlHTML(s, locale)
|
||||
return DiffInline{EscapeStatus: status, Content: content}
|
||||
}
|
||||
|
||||
// DiffInlineWithHighlightCode makes a DiffInline with code highlight and hidden unicode characters escaped
|
||||
func DiffInlineWithHighlightCode(fileName, language, code string, locale translation.Locale) DiffInline {
|
||||
highlighted, _ := highlight.Code(fileName, language, code)
|
||||
status, content := charset.EscapeControlHTML(highlighted, locale)
|
||||
return DiffInline{EscapeStatus: status, Content: content}
|
||||
func (diffSection *DiffSection) getLineContentForRender(lineIdx int, diffLine *DiffLine, fileLanguage string, highlightLines map[int]template.HTML) template.HTML {
|
||||
h, ok := highlightLines[lineIdx-1]
|
||||
if ok {
|
||||
return h
|
||||
}
|
||||
if diffLine.Content == "" {
|
||||
return ""
|
||||
}
|
||||
if setting.Git.DisableDiffHighlight {
|
||||
return template.HTML(html.EscapeString(diffLine.Content[1:]))
|
||||
}
|
||||
h, _ = highlight.Code(diffSection.Name, fileLanguage, diffLine.Content[1:])
|
||||
return h
|
||||
}
|
||||
|
||||
func (diffSection *DiffSection) getDiffLineForRender(diffLineType DiffLineType, leftLine, rightLine *DiffLine, locale translation.Locale) DiffInline {
|
||||
var fileLanguage string
|
||||
var highlightedLeftLines, highlightedRightLines map[int]template.HTML
|
||||
// when a "diff section" is manually prepared by ExcerptBlob, it doesn't have "file" information
|
||||
if diffSection.file != nil {
|
||||
fileLanguage = diffSection.file.Language
|
||||
highlightedLeftLines, highlightedRightLines = diffSection.file.highlightedLeftLines, diffSection.file.highlightedRightLines
|
||||
}
|
||||
|
||||
hcd := newHighlightCodeDiff()
|
||||
var diff1, diff2, lineHTML template.HTML
|
||||
if leftLine != nil {
|
||||
diff1 = diffSection.getLineContentForRender(leftLine.LeftIdx, leftLine, fileLanguage, highlightedLeftLines)
|
||||
lineHTML = util.Iif(diffLineType == DiffLinePlain, diff1, "")
|
||||
}
|
||||
if rightLine != nil {
|
||||
diff2 = diffSection.getLineContentForRender(rightLine.RightIdx, rightLine, fileLanguage, highlightedRightLines)
|
||||
lineHTML = util.Iif(diffLineType == DiffLinePlain, diff2, "")
|
||||
}
|
||||
if diffLineType != DiffLinePlain {
|
||||
// it seems that Gitea doesn't need the line wrapper of Chroma, so do not add them back
|
||||
// if the line wrappers are still needed in the future, it can be added back by "diffLineWithHighlightWrapper(hcd.lineWrapperTags. ...)"
|
||||
lineHTML = hcd.diffLineWithHighlight(diffLineType, diff1, diff2)
|
||||
}
|
||||
return DiffInlineWithUnicodeEscape(lineHTML, locale)
|
||||
}
|
||||
|
||||
// GetComputedInlineDiffFor computes inline diff for the given line.
|
||||
func (diffSection *DiffSection) GetComputedInlineDiffFor(diffLine *DiffLine, locale translation.Locale) DiffInline {
|
||||
if setting.Git.DisableDiffHighlight {
|
||||
return getLineContent(diffLine.Content[1:], locale)
|
||||
}
|
||||
|
||||
var (
|
||||
compareDiffLine *DiffLine
|
||||
diff1 string
|
||||
diff2 string
|
||||
)
|
||||
|
||||
language := ""
|
||||
if diffSection.file != nil {
|
||||
language = diffSection.file.Language
|
||||
}
|
||||
|
||||
// try to find equivalent diff line. ignore, otherwise
|
||||
switch diffLine.Type {
|
||||
case DiffLineSection:
|
||||
return getLineContent(diffLine.Content[1:], locale)
|
||||
case DiffLineAdd:
|
||||
compareDiffLine = diffSection.GetLine(DiffLineDel, diffLine.RightIdx)
|
||||
if compareDiffLine == nil {
|
||||
return DiffInlineWithHighlightCode(diffSection.FileName, language, diffLine.Content[1:], locale)
|
||||
}
|
||||
diff1 = compareDiffLine.Content
|
||||
diff2 = diffLine.Content
|
||||
compareDiffLine := diffSection.GetLine(DiffLineDel, diffLine.RightIdx)
|
||||
return diffSection.getDiffLineForRender(DiffLineAdd, compareDiffLine, diffLine, locale)
|
||||
case DiffLineDel:
|
||||
compareDiffLine = diffSection.GetLine(DiffLineAdd, diffLine.LeftIdx)
|
||||
if compareDiffLine == nil {
|
||||
return DiffInlineWithHighlightCode(diffSection.FileName, language, diffLine.Content[1:], locale)
|
||||
}
|
||||
diff1 = diffLine.Content
|
||||
diff2 = compareDiffLine.Content
|
||||
default:
|
||||
if strings.IndexByte(" +-", diffLine.Content[0]) > -1 {
|
||||
return DiffInlineWithHighlightCode(diffSection.FileName, language, diffLine.Content[1:], locale)
|
||||
}
|
||||
return DiffInlineWithHighlightCode(diffSection.FileName, language, diffLine.Content, locale)
|
||||
compareDiffLine := diffSection.GetLine(DiffLineAdd, diffLine.LeftIdx)
|
||||
return diffSection.getDiffLineForRender(DiffLineDel, diffLine, compareDiffLine, locale)
|
||||
default: // Plain
|
||||
// TODO: there was an "if" check: `if diffLine.Content >strings.IndexByte(" +-", diffLine.Content[0]) > -1 { ... } else { ... }`
|
||||
// no idea why it needs that check, it seems that the "if" should be always true, so try to simplify the code
|
||||
return diffSection.getDiffLineForRender(DiffLinePlain, nil, diffLine, locale)
|
||||
}
|
||||
|
||||
hcd := newHighlightCodeDiff()
|
||||
diffRecord := hcd.diffWithHighlight(diffSection.FileName, language, diff1[1:], diff2[1:])
|
||||
// it seems that Gitea doesn't need the line wrapper of Chroma, so do not add them back
|
||||
// if the line wrappers are still needed in the future, it can be added back by "diffToHTML(hcd.lineWrapperTags. ...)"
|
||||
diffHTML := diffToHTML(nil, diffRecord, diffLine.Type)
|
||||
return DiffInlineWithUnicodeEscape(template.HTML(diffHTML), locale)
|
||||
}
|
||||
|
||||
// DiffFile represents a file diff.
|
||||
type DiffFile struct {
|
||||
Name string
|
||||
NameHash string
|
||||
OldName string
|
||||
Index int
|
||||
Addition, Deletion int
|
||||
Type DiffFileType
|
||||
IsCreated bool
|
||||
IsDeleted bool
|
||||
IsBin bool
|
||||
IsLFSFile bool
|
||||
IsRenamed bool
|
||||
IsAmbiguous bool
|
||||
Sections []*DiffSection
|
||||
IsIncomplete bool
|
||||
IsIncompleteLineTooLong bool
|
||||
IsProtected bool
|
||||
IsGenerated bool
|
||||
IsVendored bool
|
||||
// only used internally to parse Ambiguous filenames
|
||||
isAmbiguous bool
|
||||
|
||||
// basic fields (parsed from diff result)
|
||||
Name string
|
||||
NameHash string
|
||||
OldName string
|
||||
Addition int
|
||||
Deletion int
|
||||
Type DiffFileType
|
||||
Mode string
|
||||
OldMode string
|
||||
IsCreated bool
|
||||
IsDeleted bool
|
||||
IsBin bool
|
||||
IsLFSFile bool
|
||||
IsRenamed bool
|
||||
IsSubmodule bool
|
||||
// basic fields but for render purpose only
|
||||
Sections []*DiffSection
|
||||
IsIncomplete bool
|
||||
IsIncompleteLineTooLong bool
|
||||
|
||||
// will be filled by the extra loop in GitDiffForRender
|
||||
Language string
|
||||
IsGenerated bool
|
||||
IsVendored bool
|
||||
SubmoduleDiffInfo *SubmoduleDiffInfo // IsSubmodule==true, then there must be a SubmoduleDiffInfo
|
||||
|
||||
// will be filled by route handler
|
||||
IsProtected bool
|
||||
|
||||
// will be filled by SyncUserSpecificDiff
|
||||
IsViewed bool // User specific
|
||||
HasChangedSinceLastReview bool // User specific
|
||||
Language string
|
||||
Mode string
|
||||
OldMode string
|
||||
|
||||
IsSubmodule bool // if IsSubmodule==true, then there must be a SubmoduleDiffInfo
|
||||
SubmoduleDiffInfo *SubmoduleDiffInfo
|
||||
// for render purpose only, will be filled by the extra loop in GitDiffForRender
|
||||
highlightedLeftLines map[int]template.HTML
|
||||
highlightedRightLines map[int]template.HTML
|
||||
}
|
||||
|
||||
// GetType returns type of diff file.
|
||||
@ -381,18 +377,23 @@ func (diffFile *DiffFile) GetType() int {
|
||||
return int(diffFile.Type)
|
||||
}
|
||||
|
||||
// GetTailSection creates a fake DiffLineSection if the last section is not the end of the file
|
||||
func (diffFile *DiffFile) GetTailSection(leftCommit, rightCommit *git.Commit) *DiffSection {
|
||||
type DiffLimitedContent struct {
|
||||
LeftContent, RightContent *limitByteWriter
|
||||
}
|
||||
|
||||
// GetTailSectionAndLimitedContent creates a fake DiffLineSection if the last section is not the end of the file
|
||||
func (diffFile *DiffFile) GetTailSectionAndLimitedContent(leftCommit, rightCommit *git.Commit) (_ *DiffSection, diffLimitedContent DiffLimitedContent) {
|
||||
if len(diffFile.Sections) == 0 || leftCommit == nil || diffFile.Type != DiffFileChange || diffFile.IsBin || diffFile.IsLFSFile {
|
||||
return nil
|
||||
return nil, diffLimitedContent
|
||||
}
|
||||
|
||||
lastSection := diffFile.Sections[len(diffFile.Sections)-1]
|
||||
lastLine := lastSection.Lines[len(lastSection.Lines)-1]
|
||||
leftLineCount := getCommitFileLineCount(leftCommit, diffFile.Name)
|
||||
rightLineCount := getCommitFileLineCount(rightCommit, diffFile.Name)
|
||||
leftLineCount, leftContent := getCommitFileLineCountAndLimitedContent(leftCommit, diffFile.Name)
|
||||
rightLineCount, rightContent := getCommitFileLineCountAndLimitedContent(rightCommit, diffFile.Name)
|
||||
diffLimitedContent = DiffLimitedContent{LeftContent: leftContent, RightContent: rightContent}
|
||||
if leftLineCount <= lastLine.LeftIdx || rightLineCount <= lastLine.RightIdx {
|
||||
return nil
|
||||
return nil, diffLimitedContent
|
||||
}
|
||||
tailDiffLine := &DiffLine{
|
||||
Type: DiffLineSection,
|
||||
@ -406,7 +407,7 @@ func (diffFile *DiffFile) GetTailSection(leftCommit, rightCommit *git.Commit) *D
|
||||
},
|
||||
}
|
||||
tailSection := &DiffSection{FileName: diffFile.Name, Lines: []*DiffLine{tailDiffLine}}
|
||||
return tailSection
|
||||
return tailSection, diffLimitedContent
|
||||
}
|
||||
|
||||
// GetDiffFileName returns the name of the diff file, or its old name in case it was deleted
|
||||
@ -438,16 +439,29 @@ func (diffFile *DiffFile) ModeTranslationKey(mode string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func getCommitFileLineCount(commit *git.Commit, filePath string) int {
|
||||
type limitByteWriter struct {
|
||||
buf bytes.Buffer
|
||||
limit int
|
||||
}
|
||||
|
||||
func (l *limitByteWriter) Write(p []byte) (n int, err error) {
|
||||
if l.buf.Len()+len(p) > l.limit {
|
||||
p = p[:l.limit-l.buf.Len()]
|
||||
}
|
||||
return l.buf.Write(p)
|
||||
}
|
||||
|
||||
func getCommitFileLineCountAndLimitedContent(commit *git.Commit, filePath string) (lineCount int, limitWriter *limitByteWriter) {
|
||||
blob, err := commit.GetBlobByPath(filePath)
|
||||
if err != nil {
|
||||
return 0
|
||||
return 0, nil
|
||||
}
|
||||
lineCount, err := blob.GetBlobLineCount()
|
||||
w := &limitByteWriter{limit: MaxDiffHighlightEntireFileSize + 1}
|
||||
lineCount, err = blob.GetBlobLineCount(w)
|
||||
if err != nil {
|
||||
return 0
|
||||
return 0, nil
|
||||
}
|
||||
return lineCount
|
||||
return lineCount, w
|
||||
}
|
||||
|
||||
// Diff represents a difference between two git trees.
|
||||
@ -526,13 +540,13 @@ parsingLoop:
|
||||
}
|
||||
|
||||
if maxFiles > -1 && len(diff.Files) >= maxFiles {
|
||||
lastFile := createDiffFile(diff, line)
|
||||
lastFile := createDiffFile(line)
|
||||
diff.End = lastFile.Name
|
||||
diff.IsIncomplete = true
|
||||
break parsingLoop
|
||||
}
|
||||
|
||||
curFile = createDiffFile(diff, line)
|
||||
curFile = createDiffFile(line)
|
||||
if skipping {
|
||||
if curFile.Name != skipToFile {
|
||||
line, err = skipToNextDiffHead(input)
|
||||
@ -615,28 +629,28 @@ parsingLoop:
|
||||
case strings.HasPrefix(line, "rename from "):
|
||||
curFile.IsRenamed = true
|
||||
curFile.Type = DiffFileRename
|
||||
if curFile.IsAmbiguous {
|
||||
if curFile.isAmbiguous {
|
||||
curFile.OldName = prepareValue(line, "rename from ")
|
||||
}
|
||||
case strings.HasPrefix(line, "rename to "):
|
||||
curFile.IsRenamed = true
|
||||
curFile.Type = DiffFileRename
|
||||
if curFile.IsAmbiguous {
|
||||
if curFile.isAmbiguous {
|
||||
curFile.Name = prepareValue(line, "rename to ")
|
||||
curFile.IsAmbiguous = false
|
||||
curFile.isAmbiguous = false
|
||||
}
|
||||
case strings.HasPrefix(line, "copy from "):
|
||||
curFile.IsRenamed = true
|
||||
curFile.Type = DiffFileCopy
|
||||
if curFile.IsAmbiguous {
|
||||
if curFile.isAmbiguous {
|
||||
curFile.OldName = prepareValue(line, "copy from ")
|
||||
}
|
||||
case strings.HasPrefix(line, "copy to "):
|
||||
curFile.IsRenamed = true
|
||||
curFile.Type = DiffFileCopy
|
||||
if curFile.IsAmbiguous {
|
||||
if curFile.isAmbiguous {
|
||||
curFile.Name = prepareValue(line, "copy to ")
|
||||
curFile.IsAmbiguous = false
|
||||
curFile.isAmbiguous = false
|
||||
}
|
||||
case strings.HasPrefix(line, "new file"):
|
||||
curFile.Type = DiffFileAdd
|
||||
@ -663,7 +677,7 @@ parsingLoop:
|
||||
curFile.IsBin = true
|
||||
case strings.HasPrefix(line, "--- "):
|
||||
// Handle ambiguous filenames
|
||||
if curFile.IsAmbiguous {
|
||||
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.
|
||||
@ -681,7 +695,7 @@ parsingLoop:
|
||||
// Otherwise do nothing with this line
|
||||
case strings.HasPrefix(line, "+++ "):
|
||||
// Handle ambiguous filenames
|
||||
if curFile.IsAmbiguous {
|
||||
if curFile.isAmbiguous {
|
||||
if len(line) > 6 && line[4] == 'b' {
|
||||
curFile.Name = line[6 : len(line)-1]
|
||||
if line[len(line)-2] == '\t' {
|
||||
@ -693,7 +707,7 @@ parsingLoop:
|
||||
} else {
|
||||
curFile.Name = curFile.OldName
|
||||
}
|
||||
curFile.IsAmbiguous = false
|
||||
curFile.isAmbiguous = false
|
||||
}
|
||||
// Otherwise do nothing with this line, but now switch to parsing hunks
|
||||
lineBytes, isFragment, err := parseHunks(ctx, curFile, maxLines, maxLineCharacters, input)
|
||||
@ -1006,7 +1020,7 @@ func parseHunks(ctx context.Context, curFile *DiffFile, maxLines, maxLineCharact
|
||||
}
|
||||
}
|
||||
|
||||
func createDiffFile(diff *Diff, line string) *DiffFile {
|
||||
func createDiffFile(line string) *DiffFile {
|
||||
// The a/ and b/ filenames are the same unless rename/copy is involved.
|
||||
// Especially, even for a creation or a deletion, /dev/null is not used
|
||||
// in place of the a/ or b/ filenames.
|
||||
@ -1017,12 +1031,11 @@ func createDiffFile(diff *Diff, line string) *DiffFile {
|
||||
//
|
||||
// Path names are quoted if necessary.
|
||||
//
|
||||
// This means that you should always be able to determine the file name even when there
|
||||
// This means that you should always be able to determine the file name even when
|
||||
// there is potential ambiguity...
|
||||
//
|
||||
// but we can be simpler with our heuristics by just forcing git to prefix things nicely
|
||||
curFile := &DiffFile{
|
||||
Index: len(diff.Files) + 1,
|
||||
Type: DiffFileChange,
|
||||
Sections: make([]*DiffSection, 0, 10),
|
||||
}
|
||||
@ -1034,7 +1047,7 @@ func createDiffFile(diff *Diff, line string) *DiffFile {
|
||||
curFile.OldName, oldNameAmbiguity = readFileName(rd)
|
||||
curFile.Name, newNameAmbiguity = readFileName(rd)
|
||||
if oldNameAmbiguity && newNameAmbiguity {
|
||||
curFile.IsAmbiguous = true
|
||||
curFile.isAmbiguous = true
|
||||
// OK we should bet that the oldName and the newName are the same if they can be made to be same
|
||||
// So we need to start again ...
|
||||
if (len(line)-len(cmdDiffHead)-1)%2 == 0 {
|
||||
@ -1121,20 +1134,21 @@ func guessBeforeCommitForDiff(gitRepo *git.Repository, beforeCommitID string, af
|
||||
return actualBeforeCommit, actualBeforeCommitID, nil
|
||||
}
|
||||
|
||||
// GetDiff builds a Diff between two commits of a repository.
|
||||
// getDiffBasic builds a Diff between two commits of a repository.
|
||||
// Passing the empty string as beforeCommitID returns a diff from the parent commit.
|
||||
// The whitespaceBehavior is either an empty string or a git flag
|
||||
func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff, error) {
|
||||
// Returned beforeCommit could be nil if the afterCommit doesn't have parent commit
|
||||
func getDiffBasic(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, files ...string) (_ *Diff, beforeCommit, afterCommit *git.Commit, err error) {
|
||||
repoPath := gitRepo.Path
|
||||
|
||||
afterCommit, err := gitRepo.GetCommit(opts.AfterCommitID)
|
||||
afterCommit, err = gitRepo.GetCommit(opts.AfterCommitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
actualBeforeCommit, actualBeforeCommitID, err := guessBeforeCommitForDiff(gitRepo, opts.BeforeCommitID, afterCommit)
|
||||
beforeCommit, beforeCommitID, err := guessBeforeCommitForDiff(gitRepo, opts.BeforeCommitID, afterCommit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
cmdDiff := git.NewCommand().
|
||||
@ -1150,7 +1164,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
|
||||
parsePatchSkipToFile = ""
|
||||
}
|
||||
|
||||
cmdDiff.AddDynamicArguments(actualBeforeCommitID.String(), opts.AfterCommitID)
|
||||
cmdDiff.AddDynamicArguments(beforeCommitID.String(), opts.AfterCommitID)
|
||||
cmdDiff.AddDashesAndList(files...)
|
||||
|
||||
cmdCtx, cmdCancel := context.WithCancel(ctx)
|
||||
@ -1180,12 +1194,25 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
|
||||
// Ensure the git process is killed if it didn't exit already
|
||||
cmdCancel()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to ParsePatch: %w", err)
|
||||
return nil, nil, nil, fmt.Errorf("unable to ParsePatch: %w", err)
|
||||
}
|
||||
diff.Start = opts.SkipTo
|
||||
return diff, beforeCommit, afterCommit, nil
|
||||
}
|
||||
|
||||
checker, deferable := gitRepo.CheckAttributeReader(opts.AfterCommitID)
|
||||
defer deferable()
|
||||
func GetDiffForAPI(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff, error) {
|
||||
diff, _, _, err := getDiffBasic(ctx, gitRepo, opts, files...)
|
||||
return diff, err
|
||||
}
|
||||
|
||||
func GetDiffForRender(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff, error) {
|
||||
diff, beforeCommit, afterCommit, err := getDiffBasic(ctx, gitRepo, opts, files...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
checker, deferrable := gitRepo.CheckAttributeReader(opts.AfterCommitID)
|
||||
defer deferrable()
|
||||
|
||||
for _, diffFile := range diff.Files {
|
||||
isVendored := optional.None[bool]()
|
||||
@ -1205,7 +1232,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
|
||||
|
||||
// Populate Submodule URLs
|
||||
if diffFile.SubmoduleDiffInfo != nil {
|
||||
diffFile.SubmoduleDiffInfo.PopulateURL(diffFile, actualBeforeCommit, afterCommit)
|
||||
diffFile.SubmoduleDiffInfo.PopulateURL(diffFile, beforeCommit, afterCommit)
|
||||
}
|
||||
|
||||
if !isVendored.Has() {
|
||||
@ -1217,15 +1244,46 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
|
||||
isGenerated = optional.Some(analyze.IsGenerated(diffFile.Name))
|
||||
}
|
||||
diffFile.IsGenerated = isGenerated.Value()
|
||||
|
||||
tailSection := diffFile.GetTailSection(actualBeforeCommit, afterCommit)
|
||||
tailSection, limitedContent := diffFile.GetTailSectionAndLimitedContent(beforeCommit, afterCommit)
|
||||
if tailSection != nil {
|
||||
diffFile.Sections = append(diffFile.Sections, tailSection)
|
||||
}
|
||||
|
||||
if !setting.Git.DisableDiffHighlight {
|
||||
if limitedContent.LeftContent != nil && limitedContent.LeftContent.buf.Len() < MaxDiffHighlightEntireFileSize {
|
||||
diffFile.highlightedLeftLines = highlightCodeLines(diffFile, true /* left */, limitedContent.LeftContent.buf.String())
|
||||
}
|
||||
if limitedContent.RightContent != nil && limitedContent.RightContent.buf.Len() < MaxDiffHighlightEntireFileSize {
|
||||
diffFile.highlightedRightLines = highlightCodeLines(diffFile, false /* right */, limitedContent.RightContent.buf.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
func highlightCodeLines(diffFile *DiffFile, isLeft bool, content string) map[int]template.HTML {
|
||||
highlightedNewContent, _ := highlight.Code(diffFile.Name, diffFile.Language, content)
|
||||
splitLines := strings.Split(string(highlightedNewContent), "\n")
|
||||
lines := make(map[int]template.HTML, len(splitLines))
|
||||
// only save the highlighted lines we need, but not the whole file, to save memory
|
||||
for _, sec := range diffFile.Sections {
|
||||
for _, ln := range sec.Lines {
|
||||
lineIdx := ln.LeftIdx
|
||||
if !isLeft {
|
||||
lineIdx = ln.RightIdx
|
||||
}
|
||||
if lineIdx >= 1 {
|
||||
idx := lineIdx - 1
|
||||
if idx < len(splitLines) {
|
||||
lines[idx] = template.HTML(splitLines[idx])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
type DiffShortStat struct {
|
||||
NumFiles, TotalAddition, TotalDeletion int
|
||||
}
|
||||
|
Reference in New Issue
Block a user