@ -31,6 +31,7 @@ import (
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"github.com/sergi/go-diff/diffmatchpatch"
"github.com/sergi/go-diff/diffmatchpatch"
stdcharset "golang.org/x/net/html/charset"
stdcharset "golang.org/x/net/html/charset"
@ -75,12 +76,12 @@ const (
// DiffLine represents a line difference in a DiffSection.
// DiffLine represents a line difference in a DiffSection.
type DiffLine struct {
type DiffLine struct {
LeftIdx int
LeftIdx int // line number, 1-based
RightIdx int
RightIdx int // line number, 1-based
Match int
Match int // line number, 1-based
Type DiffLineType
Type DiffLineType
Content string
Content string
Comments issues_model . CommentList
Comments issues_model . CommentList // related PR code comments
SectionInfo * DiffLineSectionInfo
SectionInfo * DiffLineSectionInfo
}
}
@ -95,9 +96,18 @@ type DiffLineSectionInfo struct {
RightHunkSize int
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
// BlobExcerptChunkSize represent max lines of excerpt
const BlobExcerptChunkSize = 20
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.
// GetType returns the type of DiffLine.
func ( d * DiffLine ) GetType ( ) int {
func ( d * DiffLine ) GetType ( ) int {
return int ( d . Type )
return int ( d . Type )
@ -112,8 +122,9 @@ func (d *DiffLine) GetHTMLDiffLineType() string {
return "del"
return "del"
case DiffLineSection :
case DiffLineSection :
return "tag"
return "tag"
default :
return "same"
}
}
return "same"
}
}
// CanComment returns whether a line can get commented
// CanComment returns whether a line can get commented
@ -196,38 +207,6 @@ type DiffSection struct {
Lines [ ] * DiffLine
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
// GetLine gets a specific line by type (add or del) and file line number
func ( diffSection * DiffSection ) GetLine ( lineType DiffLineType , idx int ) * DiffLine {
func ( diffSection * DiffSection ) GetLine ( lineType DiffLineType , idx int ) * DiffLine {
var (
var (
@ -271,10 +250,10 @@ LOOP:
return nil
return nil
}
}
var diffMatchPatch = diffmatchpatch . New ( )
func defaultDiffMatchPatch ( ) * diffmatchpatch . DiffMatchPatch {
dmp := diffmatchpatch . New ( )
func init ( ) {
dmp . DiffEditCost = 100
diffMatchPatch . DiffEditCost = 100
return dmp
}
}
// DiffInline is a struct that has a content and escape status
// DiffInline is a struct that has a content and escape status
@ -283,97 +262,114 @@ type DiffInline struct {
Content template . HTML
Content template . HTML
}
}
// DiffInlineWithUnicodeEscape makes a DiffInline with hidden u nicode characters escaped
// DiffInlineWithUnicodeEscape makes a DiffInline with hidden U nicode characters escaped
func DiffInlineWithUnicodeEscape ( s template . HTML , locale translation . Locale ) DiffInline {
func DiffInlineWithUnicodeEscape ( s template . HTML , locale translation . Locale ) DiffInline {
status , content := charset . EscapeControlHTML ( s , locale )
status , content := charset . EscapeControlHTML ( s , locale )
return DiffInline { EscapeStatus : status , Content : content }
return DiffInline { EscapeStatus : status , Content : content }
}
}
// DiffInlineWithHighlightCode makes a DiffInline with code highlight and hidden unicode characters escaped
func ( diffSection * DiffSection ) getLineContentForRender ( lineIdx int , diffLine * DiffLine , fileLanguage string , highlightLines map [ int ] template . HTML ) template . HTML {
func DiffInlineWithHighlightCode ( fileName , language , code string , locale translation . Locale ) DiffInline {
h , ok := highlightLines [ lineIdx - 1 ]
highlighted , _ := highlight . Code ( fileName , language , code )
if ok {
status , content := charset . EscapeControlHTML ( highlighted , locale )
return h
return DiffInline { EscapeStatus : status , Content : content }
}
}
if diffLine . Content == "" {
return ""
// GetComputedInlineDiffFor computes inline diff for the given line.
}
func ( diffSection * DiffSection ) GetComputedInlineDiffFor ( diffLine * DiffLine , locale translation . Locale ) DiffInline {
if setting . Git . DisableDiffHighlight {
if setting . Git . DisableDiffHighlight {
return getLineContent ( diffLine . Content [ 1 : ] , locale )
return template . HTML ( html . EscapeString ( diffLine . Content [ 1 : ] ) )
}
}
h , _ = highlight . Code ( diffSection . Name , fileLanguage , diffLine . Content [ 1 : ] )
return h
}
var (
func ( diffSection * DiffSection ) getDiffLineForRender ( diffLineType DiffLineType , leftLine , rightLine * DiffLine , locale translation . Locale ) DiffInline {
compareDiffLine * DiffLine
var fileLanguage string
diff1 string
var highlightedLeftLines , highlightedRightLines map [ int ] template . HTML
diff2 string
// when a "diff section" is manually prepared by ExcerptBlob, it doesn't have "file" information
)
language := ""
if diffSection . file != nil {
if diffSection . file != nil {
language = diffSection . file . Language
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 {
// try to find equivalent diff line. ignore, otherwise
// try to find equivalent diff line. ignore, otherwise
switch diffLine . Type {
switch diffLine . Type {
case DiffLineSection :
case DiffLineSection :
return getLineContent ( diffLine . Content [ 1 : ] , locale )
return getLineContent ( diffLine . Content [ 1 : ] , locale )
case DiffLineAdd :
case DiffLineAdd :
compareDiffLine = diffSection . GetLine ( DiffLineDel , diffLine . RightIdx )
compareDiffLine := diffSection . GetLine ( DiffLineDel , diffLine . RightIdx )
if compareDiffLine == nil {
return diffSection . getDiffLineForRender ( DiffLineAdd , compareDiffLine , diffLine , locale )
return DiffInlineWithHighlightCode ( diffSection . FileName , language , diffLine . Content [ 1 : ] , locale )
}
diff1 = compareDiffLine . Content
diff2 = diffLine . Content
case DiffLineDel :
case DiffLineDel :
compareDiffLine = diffSection . GetLine ( DiffLineAdd , diffLine . LeftIdx )
compareDiffLine := diffSection . GetLine ( DiffLineAdd , diffLine . LeftIdx )
if compareDiffLine == nil {
return diffSection . getDiffLineForRender ( DiffLineDel , diffLine , compareDiffLine , locale )
return DiffInlineWithHighlightCode ( diffSection . FileName , language , diffLine . Content [ 1 : ] , locale )
default : // Plain
}
// TODO: there was an "if" check: `if diffLine.Content >strings.IndexByte(" +-", diffLine.Content[0]) > -1 { ... } else { ... }`
diff1 = diffLine . Content
// no idea why it needs that check, it seems that the "if" should be always true, so try to simplify the code
diff2 = compareDiffLine . Content
return diffSection . getDiffLineForRender ( DiffLinePlain , nil , diffLine , locale )
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.
// DiffFile represents a file diff.
type DiffFile struct {
type DiffFile struct {
Name string
// only used internally to parse Ambiguous filenames
NameHash string
isAmbiguous bool
OldName string
Index int
// basic fields (parsed from diff result)
Addition , Deletion int
Name string
Type DiffFileType
NameHash string
IsCreated bool
OldName string
IsDeleted bool
Addition int
IsBin bool
Deletion int
IsLFSFile bool
Type DiffFileType
IsRenamed bool
Mode string
IsAmbiguous bool
OldMode string
Sections [ ] * DiffSection
IsCreated bool
IsIncomplete bool
IsDeleted bool
IsIncompleteLineTooLong bool
IsBin bool
IsProtected bool
IsLFSFile bool
IsGenerated bool
IsRenamed bool
IsVendored 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
IsViewed bool // User specific
HasChangedSinceLastReview bool // User specific
HasChangedSinceLastReview bool // User specific
Language string
Mode string
OldMode string
IsSubmodule bool // if IsSubmodule==true, then there must be a SubmoduleDiffInfo
// for render purpose only, will be filled by the extra loop in GitDiffForRender
SubmoduleDiffInfo * SubmoduleDiffInfo
highlightedLeftLines map [ int ] template . HTML
highlightedRightLines map [ int ] template . HTML
}
}
// GetType returns type of diff file.
// GetType returns type of diff file.
@ -381,18 +377,23 @@ func (diffFile *DiffFile) GetType() int {
return int ( diffFile . Type )
return int ( diffFile . Type )
}
}
// GetTailSection creates a fake DiffLineSection if the last section is not the end of the file
type DiffLimitedContent struct {
func ( diffFile * DiffFile ) GetTailSection ( leftCommit , rightCommit * git . Commit ) * DiffSection {
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 {
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 ]
lastSection := diffFile . Sections [ len ( diffFile . Sections ) - 1 ]
lastLine := lastSection . Lines [ len ( lastSection . Lines ) - 1 ]
lastLine := lastSection . Lines [ len ( lastSection . Lines ) - 1 ]
leftLineCount := getCommitFileLineCount ( leftCommit , diffFile . Name )
leftLineCount , leftContent := getCommitFileLineCountAndLimitedContent ( leftCommit , diffFile . Name )
rightLineCount := getCommitFileLineCount ( rightCommit , diffFile . Name )
rightLineCount , rightContent := getCommitFileLineCountAndLimitedContent ( rightCommit , diffFile . Name )
diffLimitedContent = DiffLimitedContent { LeftContent : leftContent , RightContent : rightContent }
if leftLineCount <= lastLine . LeftIdx || rightLineCount <= lastLine . RightIdx {
if leftLineCount <= lastLine . LeftIdx || rightLineCount <= lastLine . RightIdx {
return nil
return nil , diffLimitedContent
}
}
tailDiffLine := & DiffLine {
tailDiffLine := & DiffLine {
Type : DiffLineSection ,
Type : DiffLineSection ,
@ -406,7 +407,7 @@ func (diffFile *DiffFile) GetTailSection(leftCommit, rightCommit *git.Commit) *D
} ,
} ,
}
}
tailSection := & DiffSection { FileName : diffFile . Name , Lines : [ ] * DiffLine { tailDiffLine } }
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
// 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 )
blob , err := commit . GetBlobByPath ( filePath )
if err != nil {
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 {
if err != nil {
return 0
return 0 , nil
}
}
return lineCount
return lineCount , w
}
}
// Diff represents a difference between two git trees.
// Diff represents a difference between two git trees.
@ -526,13 +540,13 @@ parsingLoop:
}
}
if maxFiles > - 1 && len ( diff . Files ) >= maxFiles {
if maxFiles > - 1 && len ( diff . Files ) >= maxFiles {
lastFile := createDiffFile ( diff, line)
lastFile := createDiffFile ( line)
diff . End = lastFile . Name
diff . End = lastFile . Name
diff . IsIncomplete = true
diff . IsIncomplete = true
break parsingLoop
break parsingLoop
}
}
curFile = createDiffFile ( diff, line)
curFile = createDiffFile ( line)
if skipping {
if skipping {
if curFile . Name != skipToFile {
if curFile . Name != skipToFile {
line , err = skipToNextDiffHead ( input )
line , err = skipToNextDiffHead ( input )
@ -615,28 +629,28 @@ parsingLoop:
case strings . HasPrefix ( line , "rename from " ) :
case strings . HasPrefix ( line , "rename from " ) :
curFile . IsRenamed = true
curFile . IsRenamed = true
curFile . Type = DiffFileRename
curFile . Type = DiffFileRename
if curFile . I sAmbiguous {
if curFile . i sAmbiguous {
curFile . OldName = prepareValue ( line , "rename from " )
curFile . OldName = prepareValue ( line , "rename from " )
}
}
case strings . HasPrefix ( line , "rename to " ) :
case strings . HasPrefix ( line , "rename to " ) :
curFile . IsRenamed = true
curFile . IsRenamed = true
curFile . Type = DiffFileRename
curFile . Type = DiffFileRename
if curFile . I sAmbiguous {
if curFile . i sAmbiguous {
curFile . Name = prepareValue ( line , "rename to " )
curFile . Name = prepareValue ( line , "rename to " )
curFile . I sAmbiguous = false
curFile . i sAmbiguous = false
}
}
case strings . HasPrefix ( line , "copy from " ) :
case strings . HasPrefix ( line , "copy from " ) :
curFile . IsRenamed = true
curFile . IsRenamed = true
curFile . Type = DiffFileCopy
curFile . Type = DiffFileCopy
if curFile . I sAmbiguous {
if curFile . i sAmbiguous {
curFile . OldName = prepareValue ( line , "copy from " )
curFile . OldName = prepareValue ( line , "copy from " )
}
}
case strings . HasPrefix ( line , "copy to " ) :
case strings . HasPrefix ( line , "copy to " ) :
curFile . IsRenamed = true
curFile . IsRenamed = true
curFile . Type = DiffFileCopy
curFile . Type = DiffFileCopy
if curFile . I sAmbiguous {
if curFile . i sAmbiguous {
curFile . Name = prepareValue ( line , "copy to " )
curFile . Name = prepareValue ( line , "copy to " )
curFile . I sAmbiguous = false
curFile . i sAmbiguous = false
}
}
case strings . HasPrefix ( line , "new file" ) :
case strings . HasPrefix ( line , "new file" ) :
curFile . Type = DiffFileAdd
curFile . Type = DiffFileAdd
@ -663,7 +677,7 @@ parsingLoop:
curFile . IsBin = true
curFile . IsBin = true
case strings . HasPrefix ( line , "--- " ) :
case strings . HasPrefix ( line , "--- " ) :
// Handle ambiguous filenames
// Handle ambiguous filenames
if curFile . I sAmbiguous {
if curFile . i sAmbiguous {
// The shortest string that can end up here is:
// The shortest string that can end up here is:
// "--- a\t\n" without the quotes.
// "--- a\t\n" without the quotes.
// This line has a len() of 7 but doesn't contain a oldName.
// This line has a len() of 7 but doesn't contain a oldName.
@ -681,7 +695,7 @@ parsingLoop:
// Otherwise do nothing with this line
// Otherwise do nothing with this line
case strings . HasPrefix ( line , "+++ " ) :
case strings . HasPrefix ( line , "+++ " ) :
// Handle ambiguous filenames
// Handle ambiguous filenames
if curFile . I sAmbiguous {
if curFile . i sAmbiguous {
if len ( line ) > 6 && line [ 4 ] == 'b' {
if len ( line ) > 6 && line [ 4 ] == 'b' {
curFile . Name = line [ 6 : len ( line ) - 1 ]
curFile . Name = line [ 6 : len ( line ) - 1 ]
if line [ len ( line ) - 2 ] == '\t' {
if line [ len ( line ) - 2 ] == '\t' {
@ -693,7 +707,7 @@ parsingLoop:
} else {
} else {
curFile . Name = curFile . OldName
curFile . Name = curFile . OldName
}
}
curFile . I sAmbiguous = false
curFile . i sAmbiguous = false
}
}
// Otherwise do nothing with this line, but now switch to parsing hunks
// Otherwise do nothing with this line, but now switch to parsing hunks
lineBytes , isFragment , err := parseHunks ( ctx , curFile , maxLines , maxLineCharacters , input )
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.
// 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
// Especially, even for a creation or a deletion, /dev/null is not used
// in place of the a/ or b/ filenames.
// 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.
// 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...
// there is potential ambiguity...
//
//
// but we can be simpler with our heuristics by just forcing git to prefix things nicely
// but we can be simpler with our heuristics by just forcing git to prefix things nicely
curFile := & DiffFile {
curFile := & DiffFile {
Index : len ( diff . Files ) + 1 ,
Type : DiffFileChange ,
Type : DiffFileChange ,
Sections : make ( [ ] * DiffSection , 0 , 10 ) ,
Sections : make ( [ ] * DiffSection , 0 , 10 ) ,
}
}
@ -1034,7 +1047,7 @@ func createDiffFile(diff *Diff, line string) *DiffFile {
curFile . OldName , oldNameAmbiguity = readFileName ( rd )
curFile . OldName , oldNameAmbiguity = readFileName ( rd )
curFile . Name , newNameAmbiguity = readFileName ( rd )
curFile . Name , newNameAmbiguity = readFileName ( rd )
if oldNameAmbiguity && newNameAmbiguity {
if oldNameAmbiguity && newNameAmbiguity {
curFile . I sAmbiguous = true
curFile . i sAmbiguous = true
// OK we should bet that the oldName and the newName are the same if they can be made to be same
// 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 ...
// So we need to start again ...
if ( len ( line ) - len ( cmdDiffHead ) - 1 ) % 2 == 0 {
if ( len ( line ) - len ( cmdDiffHead ) - 1 ) % 2 == 0 {
@ -1121,20 +1134,21 @@ func guessBeforeCommitForDiff(gitRepo *git.Repository, beforeCommitID string, af
return actualBeforeCommit , actualBeforeCommitID , nil
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.
// Passing the empty string as beforeCommitID returns a diff from the parent commit.
// The whitespaceBehavior is either an empty string or a git flag
// 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
repoPath := gitRepo . Path
afterCommit , err : = gitRepo . GetCommit ( opts . AfterCommitID )
afterCommit , err = gitRepo . GetCommit ( opts . AfterCommitID )
if err != nil {
if err != nil {
return nil , err
return nil , nil , nil , err
}
}
actualBeforeCommit, actualB eforeCommitID, err := guessBeforeCommitForDiff ( gitRepo , opts . BeforeCommitID , afterCommit )
beforeCommit, b eforeCommitID, err := guessBeforeCommitForDiff ( gitRepo , opts . BeforeCommitID , afterCommit )
if err != nil {
if err != nil {
return nil , err
return nil , nil , nil , err
}
}
cmdDiff := git . NewCommand ( ) .
cmdDiff := git . NewCommand ( ) .
@ -1150,7 +1164,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
parsePatchSkipToFile = ""
parsePatchSkipToFile = ""
}
}
cmdDiff . AddDynamicArguments ( actualB eforeCommitID. String ( ) , opts . AfterCommitID )
cmdDiff . AddDynamicArguments ( b eforeCommitID. String ( ) , opts . AfterCommitID )
cmdDiff . AddDashesAndList ( files ... )
cmdDiff . AddDashesAndList ( files ... )
cmdCtx , cmdCancel := context . WithCancel ( ctx )
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
// Ensure the git process is killed if it didn't exit already
cmdCancel ( )
cmdCancel ( )
if err != nil {
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
diff . Start = opts . SkipTo
return diff , beforeCommit , afterCommit , nil
}
checker , deferable := gitRepo . CheckAttributeReader ( opts . AfterCommitID )
func GetDiffForAPI ( ctx context . Context , gitRepo * git . Repository , opts * DiffOptions , files ... string ) ( * Diff , error ) {
defer deferable ( )
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 {
for _ , diffFile := range diff . Files {
isVendored := optional . None [ bool ] ( )
isVendored := optional . None [ bool ] ( )
@ -1205,7 +1232,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
// Populate Submodule URLs
// Populate Submodule URLs
if diffFile . SubmoduleDiffInfo != nil {
if diffFile . SubmoduleDiffInfo != nil {
diffFile . SubmoduleDiffInfo . PopulateURL ( diffFile , actualB eforeCommit, afterCommit )
diffFile . SubmoduleDiffInfo . PopulateURL ( diffFile , b eforeCommit, afterCommit )
}
}
if ! isVendored . Has ( ) {
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 ) )
isGenerated = optional . Some ( analyze . IsGenerated ( diffFile . Name ) )
}
}
diffFile . IsGenerated = isGenerated . Value ( )
diffFile . IsGenerated = isGenerated . Value ( )
tailSection , limitedContent := diffFile . GetTailSectionAndLimitedContent ( beforeCommit , afterCommit )
tailSection := diffFile . GetTailSection ( actualBeforeCommit , afterCommit )
if tailSection != nil {
if tailSection != nil {
diffFile . Sections = append ( diffFile . Sections , tailSection )
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
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 {
type DiffShortStat struct {
NumFiles , TotalAddition , TotalDeletion int
NumFiles , TotalAddition , TotalDeletion int
}
}