// Copyright 2014 The Gogs Authors. All rights reserved.// Copyright 2019 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package gitdiff import ( "bufio" "bytes" "cmp" "context" "fmt" "html" "html/template" "io" "net/url" "strings" "time" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" pull_model "code.gitea.io/gitea/models/pull" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/translation" "github.com/sergi/go-diff/diffmatchpatch" stdcharset "golang.org/x/net/html/charset" "golang.org/x/text/encoding" "golang.org/x/text/transform") // DiffLineType represents the type of DiffLine.type DiffLineType uint8 // DiffLineType possible values.const ( DiffLinePlain DiffLineType = iota + 1 DiffLineAdd DiffLineDel DiffLineSection) // DiffFileType represents the type of DiffFile.type DiffFileType uint8 // DiffFileType possible values.const ( DiffFileAdd DiffFileType = iota + 1 DiffFileChange DiffFileDel DiffFileRename DiffFileCopy) // DiffLineExpandDirection represents the DiffLineSection expand directiontype DiffLineExpandDirection uint8 // DiffLineExpandDirection possible values.const ( DiffLineExpandNone DiffLineExpandDirection = iota + 1 DiffLineExpandSingle DiffLineExpandUpDown DiffLineExpandUp DiffLineExpandDown) // DiffLine represents a line difference in a DiffSection.type DiffLine struct { LeftIdx int RightIdx int Match int Type DiffLineType Content string Conversations []issues_model.CodeConversation SectionInfo *DiffLineSectionInfo} // DiffLineSectionInfo represents diff line section meta datatype DiffLineSectionInfo struct { Path string LastLeftIdx int LastRightIdx int LeftIdx int RightIdx int LeftHunkSize int RightHunkSize int} // BlobExcerptChunkSize represent max lines of excerptconst BlobExcerptChunkSize = 20 // GetType returns the type of DiffLine.func (d *DiffLine) GetType() int { return int(d.Type)} // GetHTMLDiffLineType returns the diff line type name for HTMLfunc (d *DiffLine) GetHTMLDiffLineType() string { switch d.Type { case DiffLineAdd: return "add" case DiffLineDel: return "del" case DiffLineSection: return "tag" } return "same"} // CanComment returns whether a line can get commentedfunc (d *DiffLine) CanComment() bool { return len(d.Conversations) == 0 && d.Type != DiffLineSection} // GetCommentSide returns the comment side of the first comment, if not set returns empty stringfunc (d *DiffLine) GetCommentSide() string { if len(d.Conversations) == 0 || len(d.Conversations[0]) == 0 { return "" } return d.Conversations[0][0].DiffSide()} // GetLineTypeMarker returns the line type markerfunc (d *DiffLine) GetLineTypeMarker() string { if strings.IndexByte(" +-", d.Content[0]) > -1 { return d.Content[0:1] } return ""} // GetBlobExcerptQuery builds query string to get blob excerptfunc (d *DiffLine) GetBlobExcerptQuery() string { query := fmt.Sprintf( "last_left=%d&last_right=%d&"+ "left=%d&right=%d&"+ "left_hunk_size=%d&right_hunk_size=%d&"+ "path=%s", d.SectionInfo.LastLeftIdx, d.SectionInfo.LastRightIdx, d.SectionInfo.LeftIdx, d.SectionInfo.RightIdx, d.SectionInfo.LeftHunkSize, d.SectionInfo.RightHunkSize, url.QueryEscape(d.SectionInfo.Path)) return query} // GetExpandDirection gets DiffLineExpandDirectionfunc (d *DiffLine) GetExpandDirection() DiffLineExpandDirection { if d.Type != DiffLineSection || d.SectionInfo == nil || d.SectionInfo.LeftIdx-d.SectionInfo.LastLeftIdx <= 1 || d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx <= 1 { return DiffLineExpandNone } if d.SectionInfo.LastLeftIdx <= 0 && d.SectionInfo.LastRightIdx <= 0 { return DiffLineExpandUp } else if d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx > BlobExcerptChunkSize && d.SectionInfo.RightHunkSize > 0 { return DiffLineExpandUpDown } else if d.SectionInfo.LeftHunkSize <= 0 && d.SectionInfo.RightHunkSize <= 0 { return DiffLineExpandDown } return DiffLineExpandSingle} func getDiffLineSectionInfo(treePath, line string, lastLeftIdx, lastRightIdx int) *DiffLineSectionInfo { leftLine, leftHunk, rightLine, righHunk := git.ParseDiffHunkString(line) return &DiffLineSectionInfo{ Path: treePath, LastLeftIdx: lastLeftIdx, LastRightIdx: lastRightIdx, LeftIdx: leftLine, RightIdx: rightLine, LeftHunkSize: leftHunk, RightHunkSize: righHunk, }} // escape a line's content or return <br> needed for copy/paste purposesfunc getLineContent(content string, locale translation.Locale) DiffInline { if len(content) > 0 { return DiffInlineWithUnicodeEscape(template.HTML(html.EscapeString(content)), locale) } return DiffInline{EscapeStatus: &charset.EscapeStatus{}, Content: "<br>"}} // DiffSection represents a section of a DiffFile.type DiffSection struct { file *DiffFile FileName string Name string 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 numberfunc (diffSection *DiffSection) GetLine(lineType DiffLineType, idx int) *DiffLine { var ( difference = 0 addCount = 0 delCount = 0 matchDiffLine *DiffLine ) LOOP: for _, diffLine := range diffSection.Lines { switch diffLine.Type { case DiffLineAdd: addCount++ case DiffLineDel: delCount++ default: if matchDiffLine != nil { break LOOP } difference = diffLine.RightIdx - diffLine.LeftIdx addCount = 0 delCount = 0 } switch lineType { case DiffLineDel: if diffLine.RightIdx == 0 && diffLine.LeftIdx == idx-difference { matchDiffLine = diffLine } case DiffLineAdd: if diffLine.LeftIdx == 0 && diffLine.RightIdx == idx+difference { matchDiffLine = diffLine } } } if addCount == delCount { return matchDiffLine } return nil} var diffMatchPatch = diffmatchpatch.New() func init() { diffMatchPatch.DiffEditCost = 100} // DiffInline is a struct that has a content and escape statustype DiffInline struct { EscapeStatus *charset.EscapeStatus Content template.HTML} // DiffInlineWithUnicodeEscape makes a DiffInline with hidden unicode characters escapedfunc DiffInlineWithUnicodeEscape(s template.HTML, locale translation.Locale) DiffInline { status, content := charset.EscapeControlHTML(s, locale, charset.DiffContext) return DiffInline{EscapeStatus: status, Content: content}} // DiffInlineWithHighlightCode makes a DiffInline with code highlight and hidden unicode characters escapedfunc DiffInlineWithHighlightCode(fileName, language, code string, locale translation.Locale) DiffInline { highlighted, _ := highlight.Code(fileName, language, code) status, content := charset.EscapeControlHTML(highlighted, locale, charset.DiffContext) return DiffInline{EscapeStatus: status, Content: content}} // 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 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) } 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 IsSubmodule bool Sections []*DiffSection IsIncomplete bool IsIncompleteLineTooLong bool IsProtected bool IsGenerated bool IsVendored bool IsViewed bool // User specific HasChangedSinceLastReview bool // User specific Language string Mode string OldMode string} // GetType returns type of diff file.func (diffFile *DiffFile) GetType() int { return int(diffFile.Type)} // GetTailSection creates a fake DiffLineSection if the last section is not the end of the filefunc (diffFile *DiffFile) GetTailSection(gitRepo *git.Repository, leftCommitID, rightCommitID string) *DiffSection { if len(diffFile.Sections) == 0 || diffFile.Type != DiffFileChange || diffFile.IsBin || diffFile.IsLFSFile { return nil } leftCommit, err := gitRepo.GetCommit(leftCommitID) if err != nil { return nil } rightCommit, err := gitRepo.GetCommit(rightCommitID) if err != nil { return nil } lastSection := diffFile.Sections[len(diffFile.Sections)-1] lastLine := lastSection.Lines[len(lastSection.Lines)-1] leftLineCount := getCommitFileLineCount(leftCommit, diffFile.Name) rightLineCount := getCommitFileLineCount(rightCommit, diffFile.Name) if leftLineCount <= lastLine.LeftIdx || rightLineCount <= lastLine.RightIdx { return nil } tailDiffLine := &DiffLine{ Type: DiffLineSection, Content: " ", SectionInfo: &DiffLineSectionInfo{ Path: diffFile.Name, LastLeftIdx: lastLine.LeftIdx, LastRightIdx: lastLine.RightIdx, LeftIdx: leftLineCount, RightIdx: rightLineCount, }, } tailSection := &DiffSection{FileName: diffFile.Name, Lines: []*DiffLine{tailDiffLine}} return tailSection} // GetDiffFileName returns the name of the diff file, or its old name in case it was deletedfunc (diffFile *DiffFile) GetDiffFileName() string { if diffFile.Name == "" { return diffFile.OldName } return diffFile.Name} func (diffFile *DiffFile) ShouldBeHidden() bool { return diffFile.IsGenerated || diffFile.IsViewed} func (diffFile *DiffFile) ModeTranslationKey(mode string) string { switch mode { case "040000": return "git.filemode.directory" case "100644": return "git.filemode.normal_file" case "100755": return "git.filemode.executable_file" case "120000": return "git.filemode.symbolic_link" case "160000": return "git.filemode.submodule" default: return mode }} func getCommitFileLineCount(commit *git.Commit, filePath string) int { blob, err := commit.GetBlobByPath(filePath) if err != nil { return 0 } lineCount, err := blob.GetBlobLineCount() if err != nil { return 0 } return lineCount} // Diff represents a difference between two git trees.type Diff struct { Start, End string NumFiles int TotalAddition, TotalDeletion int Files []*DiffFile IsIncomplete bool NumViewedFiles int // user-specific} // LoadComments loads comments into each linefunc (diff *Diff) LoadComments(ctx context.Context, issue *issues_model.Issue, currentUser *user_model.User, showOutdatedComments bool) error { allConversations, err := issues_model.FetchCodeConversations(ctx, issue, currentUser, showOutdatedComments) if err != nil { return err } for _, file := range diff.Files { if lineCommits, ok := allConversations[file.Name]; ok { for _, section := range file.Sections { for _, line := range section.Lines { if conversations, ok := lineCommits[int64(line.LeftIdx*-1)]; ok { line.Conversations = append(line.Conversations, conversations...) } if comments, ok := lineCommits[int64(line.RightIdx)]; ok { line.Conversations = append(line.Conversations, comments...) } } } } } return nil} const cmdDiffHead = "diff --git " // ParsePatch builds a Diff object from a io.Reader and some parameters.func ParsePatch(ctx context.Context, maxLines, maxLineCharacters, maxFiles int, reader io.Reader, skipToFile string) (*Diff, error) { log.Debug("ParsePatch(%d, %d, %d, ..., %s)", maxLines, maxLineCharacters, maxFiles, skipToFile) var curFile *DiffFile skipping := skipToFile != "" diff := &Diff{Files: make([]*DiffFile, 0)} Lines 1–500 of 1396. Load more
// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package gitdiff import ( "bufio" "bytes" "cmp" "context" "fmt" "html" "html/template" "io" "net/url" "strings" "time" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" pull_model "code.gitea.io/gitea/models/pull" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/translation" "github.com/sergi/go-diff/diffmatchpatch" stdcharset "golang.org/x/net/html/charset" "golang.org/x/text/encoding" "golang.org/x/text/transform" ) // DiffLineType represents the type of DiffLine. type DiffLineType uint8 // DiffLineType possible values. const ( DiffLinePlain DiffLineType = iota + 1 DiffLineAdd DiffLineDel DiffLineSection ) // DiffFileType represents the type of DiffFile. type DiffFileType uint8 // DiffFileType possible values. const ( DiffFileAdd DiffFileType = iota + 1 DiffFileChange DiffFileDel DiffFileRename DiffFileCopy ) // DiffLineExpandDirection represents the DiffLineSection expand direction type DiffLineExpandDirection uint8 // DiffLineExpandDirection possible values. const ( DiffLineExpandNone DiffLineExpandDirection = iota + 1 DiffLineExpandSingle DiffLineExpandUpDown DiffLineExpandUp DiffLineExpandDown ) // DiffLine represents a line difference in a DiffSection. type DiffLine struct { LeftIdx int RightIdx int Match int Type DiffLineType Content string Conversations []issues_model.CodeConversation SectionInfo *DiffLineSectionInfo } // DiffLineSectionInfo represents diff line section meta data type DiffLineSectionInfo struct { Path string LastLeftIdx int LastRightIdx int LeftIdx int RightIdx int LeftHunkSize int RightHunkSize int } // BlobExcerptChunkSize represent max lines of excerpt const BlobExcerptChunkSize = 20 // GetType returns the type of DiffLine. func (d *DiffLine) GetType() int { return int(d.Type) } // GetHTMLDiffLineType returns the diff line type name for HTML func (d *DiffLine) GetHTMLDiffLineType() string { switch d.Type { case DiffLineAdd: return "add" case DiffLineDel: return "del" case DiffLineSection: return "tag" } return "same" } // CanComment returns whether a line can get commented func (d *DiffLine) CanComment() bool { return len(d.Conversations) == 0 && d.Type != DiffLineSection } // GetCommentSide returns the comment side of the first comment, if not set returns empty string func (d *DiffLine) GetCommentSide() string { if len(d.Conversations) == 0 || len(d.Conversations[0]) == 0 { return "" } return d.Conversations[0][0].DiffSide() } // GetLineTypeMarker returns the line type marker func (d *DiffLine) GetLineTypeMarker() string { if strings.IndexByte(" +-", d.Content[0]) > -1 { return d.Content[0:1] } return "" } // GetBlobExcerptQuery builds query string to get blob excerpt func (d *DiffLine) GetBlobExcerptQuery() string { query := fmt.Sprintf( "last_left=%d&last_right=%d&"+ "left=%d&right=%d&"+ "left_hunk_size=%d&right_hunk_size=%d&"+ "path=%s", d.SectionInfo.LastLeftIdx, d.SectionInfo.LastRightIdx, d.SectionInfo.LeftIdx, d.SectionInfo.RightIdx, d.SectionInfo.LeftHunkSize, d.SectionInfo.RightHunkSize, url.QueryEscape(d.SectionInfo.Path)) return query } // GetExpandDirection gets DiffLineExpandDirection func (d *DiffLine) GetExpandDirection() DiffLineExpandDirection { if d.Type != DiffLineSection || d.SectionInfo == nil || d.SectionInfo.LeftIdx-d.SectionInfo.LastLeftIdx <= 1 || d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx <= 1 { return DiffLineExpandNone } if d.SectionInfo.LastLeftIdx <= 0 && d.SectionInfo.LastRightIdx <= 0 { return DiffLineExpandUp } else if d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx > BlobExcerptChunkSize && d.SectionInfo.RightHunkSize > 0 { return DiffLineExpandUpDown } else if d.SectionInfo.LeftHunkSize <= 0 && d.SectionInfo.RightHunkSize <= 0 { return DiffLineExpandDown } return DiffLineExpandSingle } func getDiffLineSectionInfo(treePath, line string, lastLeftIdx, lastRightIdx int) *DiffLineSectionInfo { leftLine, leftHunk, rightLine, righHunk := git.ParseDiffHunkString(line) return &DiffLineSectionInfo{ Path: treePath, LastLeftIdx: lastLeftIdx, LastRightIdx: lastRightIdx, LeftIdx: leftLine, RightIdx: rightLine, LeftHunkSize: leftHunk, RightHunkSize: righHunk, } } // escape a line's content or return <br> needed for copy/paste purposes func getLineContent(content string, locale translation.Locale) DiffInline { if len(content) > 0 { return DiffInlineWithUnicodeEscape(template.HTML(html.EscapeString(content)), locale) } return DiffInline{EscapeStatus: &charset.EscapeStatus{}, Content: "<br>"} } // DiffSection represents a section of a DiffFile. type DiffSection struct { file *DiffFile FileName string Name string 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 ( difference = 0 addCount = 0 delCount = 0 matchDiffLine *DiffLine ) LOOP: for _, diffLine := range diffSection.Lines { switch diffLine.Type { case DiffLineAdd: addCount++ case DiffLineDel: delCount++ default: if matchDiffLine != nil { break LOOP } difference = diffLine.RightIdx - diffLine.LeftIdx addCount = 0 delCount = 0 } switch lineType { case DiffLineDel: if diffLine.RightIdx == 0 && diffLine.LeftIdx == idx-difference { matchDiffLine = diffLine } case DiffLineAdd: if diffLine.LeftIdx == 0 && diffLine.RightIdx == idx+difference { matchDiffLine = diffLine } } } if addCount == delCount { return matchDiffLine } return nil } var diffMatchPatch = diffmatchpatch.New() func init() { diffMatchPatch.DiffEditCost = 100 } // DiffInline is a struct that has a content and escape status type DiffInline struct { EscapeStatus *charset.EscapeStatus Content template.HTML } // DiffInlineWithUnicodeEscape makes a DiffInline with hidden unicode characters escaped func DiffInlineWithUnicodeEscape(s template.HTML, locale translation.Locale) DiffInline { status, content := charset.EscapeControlHTML(s, locale, charset.DiffContext) 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, charset.DiffContext) return DiffInline{EscapeStatus: status, Content: content} } // 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 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) } 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 IsSubmodule bool Sections []*DiffSection IsIncomplete bool IsIncompleteLineTooLong bool IsProtected bool IsGenerated bool IsVendored bool IsViewed bool // User specific HasChangedSinceLastReview bool // User specific Language string Mode string OldMode string } // GetType returns type of diff file. 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(gitRepo *git.Repository, leftCommitID, rightCommitID string) *DiffSection { if len(diffFile.Sections) == 0 || diffFile.Type != DiffFileChange || diffFile.IsBin || diffFile.IsLFSFile { return nil } leftCommit, err := gitRepo.GetCommit(leftCommitID) if err != nil { return nil } rightCommit, err := gitRepo.GetCommit(rightCommitID) if err != nil { return nil } lastSection := diffFile.Sections[len(diffFile.Sections)-1] lastLine := lastSection.Lines[len(lastSection.Lines)-1] leftLineCount := getCommitFileLineCount(leftCommit, diffFile.Name) rightLineCount := getCommitFileLineCount(rightCommit, diffFile.Name) if leftLineCount <= lastLine.LeftIdx || rightLineCount <= lastLine.RightIdx { return nil } tailDiffLine := &DiffLine{ Type: DiffLineSection, Content: " ", SectionInfo: &DiffLineSectionInfo{ Path: diffFile.Name, LastLeftIdx: lastLine.LeftIdx, LastRightIdx: lastLine.RightIdx, LeftIdx: leftLineCount, RightIdx: rightLineCount, }, } tailSection := &DiffSection{FileName: diffFile.Name, Lines: []*DiffLine{tailDiffLine}} return tailSection } // GetDiffFileName returns the name of the diff file, or its old name in case it was deleted func (diffFile *DiffFile) GetDiffFileName() string { if diffFile.Name == "" { return diffFile.OldName } return diffFile.Name } func (diffFile *DiffFile) ShouldBeHidden() bool { return diffFile.IsGenerated || diffFile.IsViewed } func (diffFile *DiffFile) ModeTranslationKey(mode string) string { switch mode { case "040000": return "git.filemode.directory" case "100644": return "git.filemode.normal_file" case "100755": return "git.filemode.executable_file" case "120000": return "git.filemode.symbolic_link" case "160000": return "git.filemode.submodule" default: return mode } } func getCommitFileLineCount(commit *git.Commit, filePath string) int { blob, err := commit.GetBlobByPath(filePath) if err != nil { return 0 } lineCount, err := blob.GetBlobLineCount() if err != nil { return 0 } return lineCount } // Diff represents a difference between two git trees. type Diff struct { Start, End string NumFiles int TotalAddition, TotalDeletion int Files []*DiffFile IsIncomplete bool NumViewedFiles int // user-specific } // LoadComments loads comments into each line func (diff *Diff) LoadComments(ctx context.Context, issue *issues_model.Issue, currentUser *user_model.User, showOutdatedComments bool) error { allConversations, err := issues_model.FetchCodeConversations(ctx, issue, currentUser, showOutdatedComments) if err != nil { return err } for _, file := range diff.Files { if lineCommits, ok := allConversations[file.Name]; ok { for _, section := range file.Sections { for _, line := range section.Lines { if conversations, ok := lineCommits[int64(line.LeftIdx*-1)]; ok { line.Conversations = append(line.Conversations, conversations...) } if comments, ok := lineCommits[int64(line.RightIdx)]; ok { line.Conversations = append(line.Conversations, comments...) } } } } } return nil } const cmdDiffHead = "diff --git " // ParsePatch builds a Diff object from a io.Reader and some parameters. func ParsePatch(ctx context.Context, maxLines, maxLineCharacters, maxFiles int, reader io.Reader, skipToFile string) (*Diff, error) { log.Debug("ParsePatch(%d, %d, %d, ..., %s)", maxLines, maxLineCharacters, maxFiles, skipToFile) var curFile *DiffFile skipping := skipToFile != "" diff := &Diff{Files: make([]*DiffFile, 0)}