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"
"io"
"os"
"os/exec"
"strings"
"github.com/gogits/git"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
// Diff line types.
const (
DIFF_LINE_PLAIN = iota + 1
DIFF_LINE_ADD
DIFF_LINE_DEL
DIFF_LINE_SECTION
)
const (
DIFF_FILE_ADD = iota + 1
DIFF_FILE_CHANGE
DIFF_FILE_DEL
)
type DiffLine struct {
LeftIdx int
RightIdx int
Type int
Content string
}
func ( d DiffLine ) GetType ( ) int {
return d . Type
}
type DiffSection struct {
Name string
Lines [ ] * DiffLine
}
type DiffFile struct {
Name string
Addition , Deletion int
Type int
2014-04-16 04:01:20 +04:00
IsBin bool
2014-04-13 05:35:36 +04:00
Sections [ ] * DiffSection
}
type Diff struct {
TotalAddition , TotalDeletion int
Files [ ] * DiffFile
}
func ( diff * Diff ) NumFiles ( ) int {
return len ( diff . Files )
}
const DIFF_HEAD = "diff --git "
func ParsePatch ( reader io . Reader ) ( * Diff , error ) {
scanner := bufio . NewScanner ( reader )
var (
curFile * DiffFile
curSection = & DiffSection {
Lines : make ( [ ] * DiffLine , 0 , 10 ) ,
}
leftLine , rightLine int
)
diff := & Diff { Files : make ( [ ] * DiffFile , 0 ) }
var i int
for scanner . Scan ( ) {
line := scanner . Text ( )
// fmt.Println(i, line)
if strings . HasPrefix ( line , "+++ " ) || strings . HasPrefix ( line , "--- " ) {
continue
}
i = i + 1
// Diff data too large.
if i == 5000 {
log . Warn ( "Diff data too large" )
return & Diff { } , nil
}
if line == "" {
continue
}
2014-04-16 04:01:20 +04:00
switch {
case line [ 0 ] == ' ' :
2014-04-13 05:35:36 +04:00
diffLine := & DiffLine { Type : DIFF_LINE_PLAIN , Content : line , LeftIdx : leftLine , RightIdx : rightLine }
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 , "@@" )
diffLine := & DiffLine { Type : DIFF_LINE_SECTION , Content : line }
curSection . Lines = append ( curSection . Lines , diffLine )
// Parse line number.
ranges := strings . Split ( ss [ len ( ss ) - 2 ] [ 1 : ] , " " )
leftLine , _ = base . StrTo ( strings . Split ( ranges [ 0 ] , "," ) [ 0 ] [ 1 : ] ) . Int ( )
rightLine , _ = base . StrTo ( strings . Split ( ranges [ 1 ] , "," ) [ 0 ] ) . Int ( )
continue
2014-04-16 04:01:20 +04:00
case line [ 0 ] == '+' :
2014-04-13 05:35:36 +04:00
curFile . Addition ++
diff . TotalAddition ++
diffLine := & DiffLine { Type : DIFF_LINE_ADD , Content : line , RightIdx : rightLine }
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 ++
diffLine := & DiffLine { Type : DIFF_LINE_DEL , Content : line , LeftIdx : leftLine }
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.
if strings . HasPrefix ( line , DIFF_HEAD ) {
fs := strings . Split ( line [ len ( DIFF_HEAD ) : ] , " " )
a := fs [ 0 ]
curFile = & DiffFile {
Name : a [ strings . Index ( a , "/" ) + 1 : ] ,
Type : DIFF_FILE_CHANGE ,
Sections : make ( [ ] * DiffSection , 0 , 10 ) ,
}
diff . Files = append ( diff . Files , curFile )
// Check file diff type.
for scanner . Scan ( ) {
switch {
case strings . HasPrefix ( scanner . Text ( ) , "new file" ) :
curFile . Type = DIFF_FILE_ADD
case strings . HasPrefix ( scanner . Text ( ) , "deleted" ) :
curFile . Type = DIFF_FILE_DEL
case strings . HasPrefix ( scanner . Text ( ) , "index" ) :
curFile . Type = DIFF_FILE_CHANGE
}
if curFile . Type > 0 {
break
}
}
}
}
return diff , nil
}
func GetDiff ( repoPath , commitid string ) ( * Diff , error ) {
repo , err := git . OpenRepository ( repoPath )
if err != nil {
return nil , err
}
commit , err := repo . GetCommit ( commitid )
if err != nil {
return nil , err
}
// First commit of repository.
if commit . ParentCount ( ) == 0 {
rd , wr := io . Pipe ( )
go func ( ) {
cmd := exec . Command ( "git" , "show" , commitid )
cmd . Dir = repoPath
cmd . Stdout = wr
cmd . Stdin = os . Stdin
cmd . Stderr = os . Stderr
cmd . Run ( )
wr . Close ( )
} ( )
defer rd . Close ( )
return ParsePatch ( rd )
}
rd , wr := io . Pipe ( )
go func ( ) {
c , _ := commit . Parent ( 0 )
cmd := exec . Command ( "git" , "diff" , c . Id . String ( ) , commitid )
cmd . Dir = repoPath
cmd . Stdout = wr
cmd . Stdin = os . Stdin
cmd . Stderr = os . Stderr
cmd . Run ( )
wr . Close ( )
} ( )
defer rd . Close ( )
return ParsePatch ( rd )
}