2014-04-13 05:35:36 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"bufio"
2014-12-09 10:18:25 +03:00
"bytes"
2014-06-19 09:08:03 +04:00
"fmt"
2016-01-09 09:51:17 +03:00
"html"
"html/template"
2014-04-13 05:35:36 +04:00
"io"
2015-12-14 17:38:21 +03:00
"io/ioutil"
2014-04-13 05:35:36 +04:00
"os"
"os/exec"
2018-08-06 07:43:22 +03:00
"regexp"
"sort"
2018-02-10 21:19:26 +03:00
"strconv"
2014-04-13 05:35:36 +04:00
"strings"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/base"
2019-03-27 12:33:00 +03:00
"code.gitea.io/gitea/modules/git"
2016-12-06 20:58:31 +03:00
"code.gitea.io/gitea/modules/highlight"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
2016-11-11 15:11:45 +03:00
"github.com/Unknwon/com"
2016-11-05 19:56:35 +03:00
"github.com/sergi/go-diff/diffmatchpatch"
"golang.org/x/net/html/charset"
"golang.org/x/text/transform"
2014-04-13 05:35:36 +04:00
)
2016-11-24 11:30:08 +03:00
// DiffLineType represents the type of a DiffLine.
2016-01-06 23:00:40 +03:00
type DiffLineType uint8
2016-11-22 14:08:23 +03:00
// DiffLineType possible values.
2014-04-13 05:35:36 +04:00
const (
2016-11-07 19:24:59 +03:00
DiffLinePlain DiffLineType = iota + 1
DiffLineAdd
DiffLineDel
2016-11-07 19:33:03 +03:00
DiffLineSection
2014-04-13 05:35:36 +04:00
)
2016-11-24 11:30:08 +03:00
// DiffFileType represents the type of a DiffFile.
2016-01-06 23:00:40 +03:00
type DiffFileType uint8
2016-11-22 14:08:23 +03:00
// DiffFileType possible values.
2014-04-13 05:35:36 +04:00
const (
2016-11-07 19:24:59 +03:00
DiffFileAdd DiffFileType = iota + 1
DiffFileChange
DiffFileDel
2016-11-07 19:33:03 +03:00
DiffFileRename
2014-04-13 05:35:36 +04:00
)
2016-11-24 11:30:08 +03:00
// DiffLine represents a line difference in a DiffSection.
2014-04-13 05:35:36 +04:00
type DiffLine struct {
2016-06-29 18:11:00 +03:00
LeftIdx int
RightIdx int
Type DiffLineType
Content string
2018-08-06 07:43:22 +03:00
Comments [ ] * Comment
2014-04-13 05:35:36 +04:00
}
2016-11-24 11:30:08 +03:00
// GetType returns the type of a DiffLine.
2016-01-06 23:00:40 +03:00
func ( d * DiffLine ) GetType ( ) int {
return int ( d . Type )
2014-04-13 05:35:36 +04:00
}
2018-08-06 07:43:22 +03:00
// CanComment returns whether or not a line can get commented
func ( d * DiffLine ) CanComment ( ) bool {
return len ( d . Comments ) == 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 . Comments ) == 0 {
return ""
}
return d . Comments [ 0 ] . DiffSide ( )
}
2016-11-24 11:30:08 +03:00
// DiffSection represents a section of a DiffFile.
2014-04-13 05:35:36 +04:00
type DiffSection struct {
Name string
Lines [ ] * DiffLine
}
2016-01-09 09:51:17 +03:00
var (
addedCodePrefix = [ ] byte ( "<span class=\"added-code\">" )
removedCodePrefix = [ ] byte ( "<span class=\"removed-code\">" )
codeTagSuffix = [ ] byte ( "</span>" )
)
func diffToHTML ( diffs [ ] diffmatchpatch . Diff , lineType DiffLineType ) template . HTML {
2016-08-07 19:49:47 +03:00
buf := bytes . NewBuffer ( nil )
2016-08-16 17:33:53 +03:00
2017-01-05 03:50:34 +03:00
// Reproduce signs which are cut for inline diff before.
2016-08-16 17:33:53 +03:00
switch lineType {
2016-11-07 19:24:59 +03:00
case DiffLineAdd :
2016-08-16 17:33:53 +03:00
buf . WriteByte ( '+' )
2016-11-07 19:24:59 +03:00
case DiffLineDel :
2016-08-16 17:33:53 +03:00
buf . WriteByte ( '-' )
}
2016-01-09 09:51:17 +03:00
for i := range diffs {
2016-08-07 19:49:47 +03:00
switch {
2016-11-07 19:24:59 +03:00
case diffs [ i ] . Type == diffmatchpatch . DiffInsert && lineType == DiffLineAdd :
2016-01-09 09:51:17 +03:00
buf . Write ( addedCodePrefix )
buf . WriteString ( html . EscapeString ( diffs [ i ] . Text ) )
buf . Write ( codeTagSuffix )
2016-11-07 19:24:59 +03:00
case diffs [ i ] . Type == diffmatchpatch . DiffDelete && lineType == DiffLineDel :
2016-01-09 09:51:17 +03:00
buf . Write ( removedCodePrefix )
buf . WriteString ( html . EscapeString ( diffs [ i ] . Text ) )
buf . Write ( codeTagSuffix )
2016-08-07 19:49:47 +03:00
case diffs [ i ] . Type == diffmatchpatch . DiffEqual :
2016-01-09 09:51:17 +03:00
buf . WriteString ( html . EscapeString ( diffs [ i ] . Text ) )
2016-01-04 00:26:46 +03:00
}
}
2016-01-09 09:51:17 +03:00
return template . HTML ( buf . Bytes ( ) )
2016-01-04 00:26:46 +03:00
}
2016-11-22 14:08:23 +03:00
// GetLine gets a specific line by type (add or del) and file line number
2016-01-08 15:50:25 +03:00
func ( diffSection * DiffSection ) GetLine ( lineType DiffLineType , idx int ) * DiffLine {
2016-08-07 19:49:47 +03:00
var (
difference = 0
addCount = 0
delCount = 0
matchDiffLine * DiffLine
)
LOOP :
2016-01-08 15:50:25 +03:00
for _ , diffLine := range diffSection . Lines {
2016-08-07 19:49:47 +03:00
switch diffLine . Type {
2016-11-07 19:24:59 +03:00
case DiffLineAdd :
2016-08-07 19:49:47 +03:00
addCount ++
2016-11-07 19:24:59 +03:00
case DiffLineDel :
2016-08-07 19:49:47 +03:00
delCount ++
default :
if matchDiffLine != nil {
break LOOP
}
2016-01-08 15:50:25 +03:00
difference = diffLine . RightIdx - diffLine . LeftIdx
2016-08-07 19:49:47 +03:00
addCount = 0
delCount = 0
2016-01-04 00:26:46 +03:00
}
2016-08-07 19:49:47 +03:00
switch lineType {
2016-11-07 19:24:59 +03:00
case DiffLineDel :
2016-01-09 09:51:17 +03:00
if diffLine . RightIdx == 0 && diffLine . LeftIdx == idx - difference {
2016-08-07 19:49:47 +03:00
matchDiffLine = diffLine
2016-01-08 15:50:25 +03:00
}
2016-11-07 19:24:59 +03:00
case DiffLineAdd :
2016-01-09 09:51:17 +03:00
if diffLine . LeftIdx == 0 && diffLine . RightIdx == idx + difference {
2016-08-07 19:49:47 +03:00
matchDiffLine = diffLine
2016-01-04 00:26:46 +03:00
}
}
}
2016-08-07 19:49:47 +03:00
if addCount == delCount {
return matchDiffLine
}
2016-01-04 00:26:46 +03:00
return nil
}
2016-08-07 19:49:47 +03:00
var diffMatchPatch = diffmatchpatch . New ( )
func init ( ) {
diffMatchPatch . DiffEditCost = 100
}
2016-11-22 14:08:23 +03:00
// GetComputedInlineDiffFor computes inline diff for the given line.
2016-01-27 23:54:08 +03:00
func ( diffSection * DiffSection ) GetComputedInlineDiffFor ( diffLine * DiffLine ) template . HTML {
2016-08-07 19:49:47 +03:00
if setting . Git . DisableDiffHighlight {
2016-01-27 23:54:08 +03:00
return template . HTML ( html . EscapeString ( diffLine . Content [ 1 : ] ) )
}
2016-08-07 19:49:47 +03:00
var (
compareDiffLine * DiffLine
diff1 string
diff2 string
)
2016-01-04 00:26:46 +03:00
2016-01-27 23:54:08 +03:00
// try to find equivalent diff line. ignore, otherwise
2016-08-07 19:49:47 +03:00
switch diffLine . Type {
2016-11-07 19:24:59 +03:00
case DiffLineAdd :
compareDiffLine = diffSection . GetLine ( DiffLineDel , diffLine . RightIdx )
2016-01-27 23:54:08 +03:00
if compareDiffLine == nil {
2016-08-16 17:37:28 +03:00
return template . HTML ( html . EscapeString ( diffLine . Content ) )
2016-01-27 23:54:08 +03:00
}
diff1 = compareDiffLine . Content
diff2 = diffLine . Content
2016-11-07 19:24:59 +03:00
case DiffLineDel :
compareDiffLine = diffSection . GetLine ( DiffLineAdd , diffLine . LeftIdx )
2016-01-27 23:54:08 +03:00
if compareDiffLine == nil {
2016-08-16 17:37:28 +03:00
return template . HTML ( html . EscapeString ( diffLine . Content ) )
2016-01-04 00:26:46 +03:00
}
2016-01-27 23:54:08 +03:00
diff1 = diffLine . Content
diff2 = compareDiffLine . Content
2016-08-07 19:49:47 +03:00
default :
2016-08-16 17:33:53 +03:00
return template . HTML ( html . EscapeString ( diffLine . Content ) )
2016-01-27 23:54:08 +03:00
}
2016-01-04 00:26:46 +03:00
2016-08-07 19:49:47 +03:00
diffRecord := diffMatchPatch . DiffMain ( diff1 [ 1 : ] , diff2 [ 1 : ] , true )
diffRecord = diffMatchPatch . DiffCleanupEfficiency ( diffRecord )
2016-01-04 00:26:46 +03:00
2016-01-27 23:54:08 +03:00
return diffToHTML ( diffRecord , diffLine . Type )
2016-01-04 00:26:46 +03:00
}
2016-11-24 11:30:08 +03:00
// DiffFile represents a file diff.
2014-04-13 05:35:36 +04:00
type DiffFile struct {
Name string
2015-11-03 03:55:24 +03:00
OldName string
2014-05-13 20:40:32 +04:00
Index int
2014-04-13 05:35:36 +04:00
Addition , Deletion int
2016-01-06 23:00:40 +03:00
Type DiffFileType
2015-02-06 12:02:32 +03:00
IsCreated bool
IsDeleted bool
2014-04-16 04:01:20 +04:00
IsBin bool
2016-12-26 04:16:37 +03:00
IsLFSFile bool
2015-11-03 03:55:24 +03:00
IsRenamed bool
2016-07-22 21:18:56 +03:00
IsSubmodule bool
2014-04-13 05:35:36 +04:00
Sections [ ] * DiffSection
2016-06-29 18:11:00 +03:00
IsIncomplete bool
2014-04-13 05:35:36 +04:00
}
2016-11-22 14:08:23 +03:00
// GetType returns type of diff file.
2016-01-06 23:00:40 +03:00
func ( diffFile * DiffFile ) GetType ( ) int {
return int ( diffFile . Type )
}
2016-11-24 11:30:08 +03:00
// GetHighlightClass returns highlight class for a filename.
2016-01-31 19:19:02 +03:00
func ( diffFile * DiffFile ) GetHighlightClass ( ) string {
2016-02-22 00:45:24 +03:00
return highlight . FileNameToHighlightClass ( diffFile . Name )
2016-01-31 19:19:02 +03:00
}
2016-11-24 11:30:08 +03:00
// Diff represents a difference between two git trees.
2014-04-13 05:35:36 +04:00
type Diff struct {
TotalAddition , TotalDeletion int
Files [ ] * DiffFile
2016-06-29 18:11:00 +03:00
IsIncomplete bool
2014-04-13 05:35:36 +04:00
}
2018-08-06 07:43:22 +03:00
// LoadComments loads comments into each line
func ( diff * Diff ) LoadComments ( issue * Issue , currentUser * User ) error {
allComments , err := FetchCodeComments ( issue , currentUser )
if err != nil {
return err
}
for _ , file := range diff . Files {
if lineCommits , ok := allComments [ file . Name ] ; ok {
for _ , section := range file . Sections {
for _ , line := range section . Lines {
if comments , ok := lineCommits [ int64 ( line . LeftIdx * - 1 ) ] ; ok {
line . Comments = append ( line . Comments , comments ... )
}
if comments , ok := lineCommits [ int64 ( line . RightIdx ) ] ; ok {
line . Comments = append ( line . Comments , comments ... )
}
sort . SliceStable ( line . Comments , func ( i , j int ) bool {
return line . Comments [ i ] . CreatedUnix < line . Comments [ j ] . CreatedUnix
} )
}
}
}
}
return nil
}
2016-11-24 11:30:08 +03:00
// NumFiles returns number of files changes in a diff.
2014-04-13 05:35:36 +04:00
func ( diff * Diff ) NumFiles ( ) int {
return len ( diff . Files )
}
2018-08-06 07:43:22 +03:00
// Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9]
2018-10-18 05:03:49 +03:00
var hunkRegex = regexp . MustCompile ( ` ^@@ -(?P<beginOld>[0-9]+)(,(?P<endOld>[0-9]+))? \+(?P<beginNew>[0-9]+)(,(?P<endNew>[0-9]+))? @@ ` )
2018-08-06 07:43:22 +03:00
func isHeader ( lof string ) bool {
return strings . HasPrefix ( lof , cmdDiffHead ) || strings . HasPrefix ( lof , "---" ) || strings . HasPrefix ( lof , "+++" )
}
// CutDiffAroundLine cuts a diff of a file in way that only the given line + numberOfLine above it will be shown
// it also recalculates hunks and adds the appropriate headers to the new diff.
// Warning: Only one-file diffs are allowed.
func CutDiffAroundLine ( originalDiff io . Reader , line int64 , old bool , numbersOfLine int ) string {
if line == 0 || numbersOfLine == 0 {
// no line or num of lines => no diff
return ""
}
scanner := bufio . NewScanner ( originalDiff )
hunk := make ( [ ] string , 0 )
// begin is the start of the hunk containing searched line
// end is the end of the hunk ...
// currentLine is the line number on the side of the searched line (differentiated by old)
// otherLine is the line number on the opposite side of the searched line (differentiated by old)
var begin , end , currentLine , otherLine int64
var headerLines int
for scanner . Scan ( ) {
lof := scanner . Text ( )
// Add header to enable parsing
if isHeader ( lof ) {
hunk = append ( hunk , lof )
headerLines ++
}
if currentLine > line {
break
}
// Detect "hunk" with contains commented lof
if strings . HasPrefix ( lof , "@@" ) {
// Already got our hunk. End of hunk detected!
if len ( hunk ) > headerLines {
break
}
2018-10-18 05:03:49 +03:00
// A map with named groups of our regex to recognize them later more easily
submatches := hunkRegex . FindStringSubmatch ( lof )
groups := make ( map [ string ] string )
for i , name := range hunkRegex . SubexpNames ( ) {
if i != 0 && name != "" {
groups [ name ] = submatches [ i ]
}
}
2018-08-06 07:43:22 +03:00
if old {
2018-10-18 05:03:49 +03:00
begin = com . StrTo ( groups [ "beginOld" ] ) . MustInt64 ( )
end = com . StrTo ( groups [ "endOld" ] ) . MustInt64 ( )
2018-08-06 07:43:22 +03:00
// init otherLine with begin of opposite side
2018-10-18 05:03:49 +03:00
otherLine = com . StrTo ( groups [ "beginNew" ] ) . MustInt64 ( )
2018-08-06 07:43:22 +03:00
} else {
2018-10-18 05:03:49 +03:00
begin = com . StrTo ( groups [ "beginNew" ] ) . MustInt64 ( )
if groups [ "endNew" ] != "" {
end = com . StrTo ( groups [ "endNew" ] ) . MustInt64 ( )
2018-09-17 17:59:49 +03:00
} else {
end = 0
}
2018-08-06 07:43:22 +03:00
// init otherLine with begin of opposite side
2018-10-18 05:03:49 +03:00
otherLine = com . StrTo ( groups [ "beginOld" ] ) . MustInt64 ( )
2018-08-06 07:43:22 +03:00
}
end += begin // end is for real only the number of lines in hunk
// lof is between begin and end
if begin <= line && end >= line {
hunk = append ( hunk , lof )
currentLine = begin
continue
}
} else if len ( hunk ) > headerLines {
hunk = append ( hunk , lof )
// Count lines in context
switch lof [ 0 ] {
case '+' :
if ! old {
currentLine ++
} else {
otherLine ++
}
case '-' :
if old {
currentLine ++
} else {
otherLine ++
}
default :
currentLine ++
otherLine ++
}
}
}
// No hunk found
if currentLine == 0 {
return ""
}
// headerLines + hunkLine (1) = totalNonCodeLines
if len ( hunk ) - headerLines - 1 <= numbersOfLine {
// No need to cut the hunk => return existing hunk
return strings . Join ( hunk , "\n" )
}
var oldBegin , oldNumOfLines , newBegin , newNumOfLines int64
if old {
oldBegin = currentLine
newBegin = otherLine
} else {
oldBegin = otherLine
newBegin = currentLine
}
// headers + hunk header
newHunk := make ( [ ] string , headerLines )
// transfer existing headers
for idx , lof := range hunk [ : headerLines ] {
newHunk [ idx ] = lof
}
// transfer last n lines
for _ , lof := range hunk [ len ( hunk ) - numbersOfLine - 1 : ] {
newHunk = append ( newHunk , lof )
}
// calculate newBegin, ... by counting lines
for i := len ( hunk ) - 1 ; i >= len ( hunk ) - numbersOfLine ; i -- {
switch hunk [ i ] [ 0 ] {
case '+' :
newBegin --
newNumOfLines ++
case '-' :
oldBegin --
oldNumOfLines ++
default :
oldBegin --
newBegin --
newNumOfLines ++
oldNumOfLines ++
}
}
// construct the new hunk header
newHunk [ headerLines ] = fmt . Sprintf ( "@@ -%d,%d +%d,%d @@" ,
oldBegin , oldNumOfLines , newBegin , newNumOfLines )
return strings . Join ( newHunk , "\n" )
}
2016-11-22 14:08:23 +03:00
const cmdDiffHead = "diff --git "
2014-04-13 05:35:36 +04:00
2016-11-24 11:30:08 +03:00
// ParsePatch builds a Diff object from a io.Reader and some
// parameters.
2016-07-30 18:39:58 +03:00
// TODO: move this function to gogits/git-module
2017-01-05 03:50:34 +03:00
func ParsePatch ( maxLines , maxLineCharacters , maxFiles int , reader io . Reader ) ( * Diff , error ) {
2014-04-13 05:35:36 +04:00
var (
2015-12-02 09:10:13 +03:00
diff = & Diff { Files : make ( [ ] * DiffFile , 0 ) }
2017-12-12 03:32:36 +03:00
curFile = & DiffFile { }
2014-04-13 05:35:36 +04:00
curSection = & DiffSection {
Lines : make ( [ ] * DiffLine , 0 , 10 ) ,
}
leftLine , rightLine int
2015-12-02 09:10:13 +03:00
lineCount int
2016-06-29 18:11:00 +03:00
curFileLinesCount int
2016-12-26 04:16:37 +03:00
curFileLFSPrefix bool
2014-04-13 05:35:36 +04:00
)
2015-12-02 09:10:13 +03:00
input := bufio . NewReader ( reader )
isEOF := false
2016-06-29 18:11:00 +03:00
for ! isEOF {
2017-11-29 02:22:24 +03:00
var linebuf bytes . Buffer
for {
b , err := input . ReadByte ( )
if err != nil {
if err == io . EOF {
isEOF = true
break
} else {
return nil , fmt . Errorf ( "ReadByte: %v" , err )
}
}
if b == '\n' {
break
}
if linebuf . Len ( ) < maxLineCharacters {
linebuf . WriteByte ( b )
} else if linebuf . Len ( ) == maxLineCharacters {
curFile . IsIncomplete = true
2015-12-02 09:10:13 +03:00
}
}
2017-11-29 02:22:24 +03:00
line := linebuf . String ( )
2014-04-13 05:35:36 +04:00
2016-06-29 18:11:00 +03:00
if strings . HasPrefix ( line , "+++ " ) || strings . HasPrefix ( line , "--- " ) || len ( line ) == 0 {
2014-09-17 08:03:03 +04:00
continue
}
2016-12-26 04:16:37 +03:00
trimLine := strings . Trim ( line , "+- " )
if trimLine == LFSMetaFileIdentifier {
curFileLFSPrefix = true
}
if curFileLFSPrefix && strings . HasPrefix ( trimLine , LFSMetaFileOidPrefix ) {
oid := strings . TrimPrefix ( trimLine , LFSMetaFileOidPrefix )
if len ( oid ) == 64 {
m := & LFSMetaObject { Oid : oid }
count , err := x . Count ( m )
if err == nil && count > 0 {
curFile . IsBin = true
curFile . IsLFSFile = true
curSection . Lines = nil
}
}
}
2016-06-29 18:11:00 +03:00
curFileLinesCount ++
2015-12-02 09:10:13 +03:00
lineCount ++
2014-04-13 05:35:36 +04:00
2017-01-05 03:50:34 +03:00
// Diff data too large, we only show the first about maxLines lines
2017-11-29 02:22:24 +03:00
if curFileLinesCount >= maxLines {
2016-06-29 18:11:00 +03:00
curFile . IsIncomplete = true
2014-04-13 05:35:36 +04:00
}
2014-04-16 04:01:20 +04:00
switch {
case line [ 0 ] == ' ' :
2016-11-07 19:24:59 +03:00
diffLine := & DiffLine { Type : DiffLinePlain , Content : line , LeftIdx : leftLine , RightIdx : rightLine }
2014-04-13 05:35:36 +04:00
leftLine ++
rightLine ++
curSection . Lines = append ( curSection . Lines , diffLine )
continue
2014-04-16 04:01:20 +04:00
case line [ 0 ] == '@' :
2014-04-13 05:35:36 +04:00
curSection = & DiffSection { }
curFile . Sections = append ( curFile . Sections , curSection )
ss := strings . Split ( line , "@@" )
2016-11-07 19:33:03 +03:00
diffLine := & DiffLine { Type : DiffLineSection , Content : line }
2014-04-13 05:35:36 +04:00
curSection . Lines = append ( curSection . Lines , diffLine )
// Parse line number.
2015-07-29 17:55:01 +03:00
ranges := strings . Split ( ss [ 1 ] [ 1 : ] , " " )
2014-07-26 08:24:27 +04:00
leftLine , _ = com . StrTo ( strings . Split ( ranges [ 0 ] , "," ) [ 0 ] [ 1 : ] ) . Int ( )
2015-07-29 17:55:01 +03:00
if len ( ranges ) > 1 {
rightLine , _ = com . StrTo ( strings . Split ( ranges [ 1 ] , "," ) [ 0 ] ) . Int ( )
} else {
log . Warn ( "Parse line number failed: %v" , line )
rightLine = leftLine
}
2014-04-13 05:35:36 +04:00
continue
2014-04-16 04:01:20 +04:00
case line [ 0 ] == '+' :
2014-04-13 05:35:36 +04:00
curFile . Addition ++
diff . TotalAddition ++
2016-11-07 19:24:59 +03:00
diffLine := & DiffLine { Type : DiffLineAdd , Content : line , RightIdx : rightLine }
2014-04-13 05:35:36 +04:00
rightLine ++
curSection . Lines = append ( curSection . Lines , diffLine )
continue
2014-04-16 04:01:20 +04:00
case line [ 0 ] == '-' :
2014-04-13 05:35:36 +04:00
curFile . Deletion ++
diff . TotalDeletion ++
2016-11-07 19:24:59 +03:00
diffLine := & DiffLine { Type : DiffLineDel , Content : line , LeftIdx : leftLine }
2014-04-13 05:35:36 +04:00
if leftLine > 0 {
leftLine ++
}
curSection . Lines = append ( curSection . Lines , diffLine )
2014-04-16 04:01:20 +04:00
case strings . HasPrefix ( line , "Binary" ) :
curFile . IsBin = true
2014-04-13 05:35:36 +04:00
continue
}
// Get new file.
2016-11-22 14:08:23 +03:00
if strings . HasPrefix ( line , cmdDiffHead ) {
2015-11-03 03:55:24 +03:00
middle := - 1
2015-11-20 09:18:50 +03:00
// Note: In case file name is surrounded by double quotes (it happens only in git-shell).
// e.g. diff --git "a/xxx" "b/xxx"
2016-11-22 14:08:23 +03:00
hasQuote := line [ len ( cmdDiffHead ) ] == '"'
2015-11-03 03:55:24 +03:00
if hasQuote {
middle = strings . Index ( line , ` "b/ ` )
} else {
middle = strings . Index ( line , " b/" )
}
2014-04-13 05:35:36 +04:00
2016-11-22 14:08:23 +03:00
beg := len ( cmdDiffHead )
2015-11-03 03:55:24 +03:00
a := line [ beg + 2 : middle ]
b := line [ middle + 3 : ]
2019-03-14 19:09:53 +03:00
2015-11-03 03:55:24 +03:00
if hasQuote {
2019-03-14 19:09:53 +03:00
// Keep the entire string in double quotes for now
a = line [ beg : middle ]
b = line [ middle + 1 : ]
2018-02-10 21:19:26 +03:00
var err error
a , err = strconv . Unquote ( a )
if err != nil {
return nil , fmt . Errorf ( "Unquote: %v" , err )
}
b , err = strconv . Unquote ( b )
if err != nil {
return nil , fmt . Errorf ( "Unquote: %v" , err )
}
2019-03-14 19:09:53 +03:00
// Now remove the /a /b
a = a [ 2 : ]
b = b [ 2 : ]
2015-08-20 11:08:26 +03:00
}
2014-04-13 05:35:36 +04:00
curFile = & DiffFile {
2017-06-14 12:07:09 +03:00
Name : b ,
OldName : a ,
Index : len ( diff . Files ) + 1 ,
Type : DiffFileChange ,
Sections : make ( [ ] * DiffSection , 0 , 10 ) ,
IsRenamed : a != b ,
2014-04-13 05:35:36 +04:00
}
diff . Files = append ( diff . Files , curFile )
2016-06-29 18:11:00 +03:00
if len ( diff . Files ) >= maxFiles {
diff . IsIncomplete = true
io . Copy ( ioutil . Discard , reader )
break
}
curFileLinesCount = 0
2016-12-26 04:16:37 +03:00
curFileLFSPrefix = false
2014-04-13 05:35:36 +04:00
2016-07-22 21:18:56 +03:00
// Check file diff type and is submodule.
2015-12-02 09:10:13 +03:00
for {
line , err := input . ReadString ( '\n' )
if err != nil {
if err == io . EOF {
isEOF = true
} else {
return nil , fmt . Errorf ( "ReadString: %v" , err )
}
}
2014-04-13 05:35:36 +04:00
switch {
2015-12-02 09:10:13 +03:00
case strings . HasPrefix ( line , "new file" ) :
2016-11-07 19:24:59 +03:00
curFile . Type = DiffFileAdd
2015-02-06 12:02:32 +03:00
curFile . IsCreated = true
2015-12-02 09:10:13 +03:00
case strings . HasPrefix ( line , "deleted" ) :
2016-11-07 19:24:59 +03:00
curFile . Type = DiffFileDel
2015-02-06 12:02:32 +03:00
curFile . IsDeleted = true
2015-12-02 09:10:13 +03:00
case strings . HasPrefix ( line , "index" ) :
2016-11-07 19:24:59 +03:00
curFile . Type = DiffFileChange
2015-12-02 09:10:13 +03:00
case strings . HasPrefix ( line , "similarity index 100%" ) :
2016-11-07 19:33:03 +03:00
curFile . Type = DiffFileRename
2014-04-13 05:35:36 +04:00
}
if curFile . Type > 0 {
2016-07-22 21:18:56 +03:00
if strings . HasSuffix ( line , " 160000\n" ) {
curFile . IsSubmodule = true
}
2014-04-13 05:35:36 +04:00
break
}
}
}
}
2015-12-02 09:10:13 +03:00
// FIXME: detect encoding while parsing.
var buf bytes . Buffer
2015-07-29 17:55:01 +03:00
for _ , f := range diff . Files {
buf . Reset ( )
for _ , sec := range f . Sections {
for _ , l := range sec . Lines {
buf . WriteString ( l . Content )
buf . WriteString ( "\n" )
}
}
2016-01-01 06:13:47 +03:00
charsetLabel , err := base . DetectEncoding ( buf . Bytes ( ) )
if charsetLabel != "UTF-8" && err == nil {
2015-07-29 17:55:01 +03:00
encoding , _ := charset . Lookup ( charsetLabel )
if encoding != nil {
d := encoding . NewDecoder ( )
2014-12-09 10:18:25 +03:00
for _ , sec := range f . Sections {
for _ , l := range sec . Lines {
2014-12-22 12:01:52 +03:00
if c , _ , err := transform . String ( d , l . Content ) ; err == nil {
l . Content = c
}
2014-12-09 10:18:25 +03:00
}
}
}
}
}
2019-03-14 19:09:53 +03:00
2014-04-13 05:35:36 +04:00
return diff , nil
}
2016-11-24 11:30:08 +03:00
// GetDiffRange builds a Diff between two commits of a repository.
// passing the empty string as beforeCommitID returns a diff from the
// parent commit.
2017-01-05 03:50:34 +03:00
func GetDiffRange ( repoPath , beforeCommitID , afterCommitID string , maxLines , maxLineCharacters , maxFiles int ) ( * Diff , error ) {
2018-08-14 20:49:33 +03:00
return GetDiffRangeWithWhitespaceBehavior ( repoPath , beforeCommitID , afterCommitID , maxLines , maxLineCharacters , maxFiles , "" )
}
// GetDiffRangeWithWhitespaceBehavior 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 GetDiffRangeWithWhitespaceBehavior ( repoPath , beforeCommitID , afterCommitID string , maxLines , maxLineCharacters , maxFiles int , whitespaceBehavior string ) ( * Diff , error ) {
2016-07-30 18:39:58 +03:00
gitRepo , err := git . OpenRepository ( repoPath )
2014-04-13 05:35:36 +04:00
if err != nil {
return nil , err
}
2016-07-30 18:39:58 +03:00
commit , err := gitRepo . GetCommit ( afterCommitID )
2014-04-13 05:35:36 +04:00
if err != nil {
return nil , err
}
2014-05-29 06:15:15 +04:00
var cmd * exec . Cmd
2018-08-14 20:49:33 +03:00
if len ( beforeCommitID ) == 0 && commit . ParentCount ( ) == 0 {
cmd = exec . Command ( "git" , "show" , afterCommitID )
2014-05-29 06:15:15 +04:00
} else {
2018-08-14 20:49:33 +03:00
actualBeforeCommitID := beforeCommitID
if len ( actualBeforeCommitID ) == 0 {
parentCommit , _ := commit . Parent ( 0 )
actualBeforeCommitID = parentCommit . ID . String ( )
}
diffArgs := [ ] string { "diff" , "-M" }
if len ( whitespaceBehavior ) != 0 {
diffArgs = append ( diffArgs , whitespaceBehavior )
}
diffArgs = append ( diffArgs , actualBeforeCommitID )
diffArgs = append ( diffArgs , afterCommitID )
cmd = exec . Command ( "git" , diffArgs ... )
2014-04-13 05:35:36 +04:00
}
2014-05-29 06:15:15 +04:00
cmd . Dir = repoPath
cmd . Stderr = os . Stderr
2014-07-07 01:32:36 +04:00
2015-12-02 09:10:13 +03:00
stdout , err := cmd . StdoutPipe ( )
if err != nil {
return nil , fmt . Errorf ( "StdoutPipe: %v" , err )
}
if err = cmd . Start ( ) ; err != nil {
return nil , fmt . Errorf ( "Start: %v" , err )
}
2014-07-07 01:32:36 +04:00
2017-01-17 08:58:58 +03:00
pid := process . GetManager ( ) . Add ( fmt . Sprintf ( "GetDiffRange [repo_path: %s]" , repoPath ) , cmd )
defer process . GetManager ( ) . Remove ( pid )
2015-12-02 09:10:13 +03:00
2017-01-05 03:50:34 +03:00
diff , err := ParsePatch ( maxLines , maxLineCharacters , maxFiles , stdout )
2015-12-02 09:10:13 +03:00
if err != nil {
return nil , fmt . Errorf ( "ParsePatch: %v" , err )
}
if err = cmd . Wait ( ) ; err != nil {
return nil , fmt . Errorf ( "Wait: %v" , err )
}
return diff , nil
2014-04-13 05:35:36 +04:00
}
2014-08-26 16:20:18 +04:00
2016-11-24 11:30:08 +03:00
// RawDiffType type of a raw diff.
2016-07-30 18:39:58 +03:00
type RawDiffType string
2016-11-22 14:08:23 +03:00
// RawDiffType possible values.
2016-07-30 18:39:58 +03:00
const (
2016-11-07 19:24:59 +03:00
RawDiffNormal RawDiffType = "diff"
RawDiffPatch RawDiffType = "patch"
2016-07-30 18:39:58 +03:00
)
// GetRawDiff dumps diff results of repository in given commit ID to io.Writer.
// TODO: move this function to gogits/git-module
func GetRawDiff ( repoPath , commitID string , diffType RawDiffType , writer io . Writer ) error {
2018-08-06 07:43:22 +03:00
return GetRawDiffForFile ( repoPath , "" , commitID , diffType , "" , writer )
}
// GetRawDiffForFile dumps diff results of file in given commit ID to io.Writer.
// TODO: move this function to gogits/git-module
func GetRawDiffForFile ( repoPath , startCommit , endCommit string , diffType RawDiffType , file string , writer io . Writer ) error {
2016-07-30 18:02:22 +03:00
repo , err := git . OpenRepository ( repoPath )
if err != nil {
2016-07-30 18:39:58 +03:00
return fmt . Errorf ( "OpenRepository: %v" , err )
2016-07-30 18:02:22 +03:00
}
2018-08-06 07:43:22 +03:00
commit , err := repo . GetCommit ( endCommit )
2016-07-30 18:02:22 +03:00
if err != nil {
2016-07-30 18:39:58 +03:00
return fmt . Errorf ( "GetCommit: %v" , err )
2016-07-30 18:02:22 +03:00
}
2018-08-06 07:43:22 +03:00
fileArgs := make ( [ ] string , 0 )
if len ( file ) > 0 {
fileArgs = append ( fileArgs , "--" , file )
}
2016-07-30 18:02:22 +03:00
var cmd * exec . Cmd
switch diffType {
2016-11-07 19:24:59 +03:00
case RawDiffNormal :
2018-08-06 07:43:22 +03:00
if len ( startCommit ) != 0 {
cmd = exec . Command ( "git" , append ( [ ] string { "diff" , "-M" , startCommit , endCommit } , fileArgs ... ) ... )
} else if commit . ParentCount ( ) == 0 {
cmd = exec . Command ( "git" , append ( [ ] string { "show" , endCommit } , fileArgs ... ) ... )
2016-07-30 18:02:22 +03:00
} else {
c , _ := commit . Parent ( 0 )
2018-08-06 07:43:22 +03:00
cmd = exec . Command ( "git" , append ( [ ] string { "diff" , "-M" , c . ID . String ( ) , endCommit } , fileArgs ... ) ... )
2016-07-30 18:02:22 +03:00
}
2016-11-07 19:24:59 +03:00
case RawDiffPatch :
2018-08-06 07:43:22 +03:00
if len ( startCommit ) != 0 {
query := fmt . Sprintf ( "%s...%s" , endCommit , startCommit )
cmd = exec . Command ( "git" , append ( [ ] string { "format-patch" , "--no-signature" , "--stdout" , "--root" , query } , fileArgs ... ) ... )
} else if commit . ParentCount ( ) == 0 {
cmd = exec . Command ( "git" , append ( [ ] string { "format-patch" , "--no-signature" , "--stdout" , "--root" , endCommit } , fileArgs ... ) ... )
2016-07-30 18:02:22 +03:00
} else {
c , _ := commit . Parent ( 0 )
2018-08-06 07:43:22 +03:00
query := fmt . Sprintf ( "%s...%s" , endCommit , c . ID . String ( ) )
cmd = exec . Command ( "git" , append ( [ ] string { "format-patch" , "--no-signature" , "--stdout" , query } , fileArgs ... ) ... )
2016-07-30 18:02:22 +03:00
}
default :
2016-07-30 18:39:58 +03:00
return fmt . Errorf ( "invalid diffType: %s" , diffType )
2016-07-30 18:02:22 +03:00
}
stderr := new ( bytes . Buffer )
cmd . Dir = repoPath
2016-07-30 18:39:58 +03:00
cmd . Stdout = writer
2016-07-30 18:02:22 +03:00
cmd . Stderr = stderr
2016-07-30 18:39:58 +03:00
if err = cmd . Run ( ) ; err != nil {
return fmt . Errorf ( "Run: %v - %s" , err , stderr )
2016-07-30 18:02:22 +03:00
}
2016-07-30 18:39:58 +03:00
return nil
2016-07-30 18:02:22 +03:00
}
2016-11-24 11:30:08 +03:00
// GetDiffCommit builds a Diff representing the given commitID.
2017-01-05 03:50:34 +03:00
func GetDiffCommit ( repoPath , commitID string , maxLines , maxLineCharacters , maxFiles int ) ( * Diff , error ) {
return GetDiffRange ( repoPath , "" , commitID , maxLines , maxLineCharacters , maxFiles )
2014-08-26 16:20:18 +04:00
}