2014-04-13 09:35:36 +08: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-06-19 01:08:03 -04:00
"fmt"
2014-04-13 09:35:36 +08:00
"io"
"os"
"os/exec"
"strings"
2014-07-06 17:32:36 -04:00
"time"
2014-04-13 09:35:36 +08:00
2014-07-26 00:24:27 -04:00
"github.com/Unknwon/com"
2014-09-12 19:42:11 -04:00
"github.com/gogits/gogs/modules/git"
2014-04-13 09:35:36 +08:00
"github.com/gogits/gogs/modules/log"
2014-06-19 01:08:03 -04:00
"github.com/gogits/gogs/modules/process"
2014-04-13 09:35:36 +08:00
)
// 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
2014-05-13 12:40:32 -04:00
Index int
2014-04-13 09:35:36 +08:00
Addition , Deletion int
Type int
2014-04-15 20:01:20 -04:00
IsBin bool
2014-04-13 09:35:36 +08: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 "
2014-09-17 12:03:03 +08:00
func ParsePatch ( pid int64 , maxlines int , cmd * exec . Cmd , reader io . Reader ) ( * Diff , error ) {
2014-04-13 09:35:36 +08:00
scanner := bufio . NewScanner ( reader )
var (
curFile * DiffFile
curSection = & DiffSection {
Lines : make ( [ ] * DiffLine , 0 , 10 ) ,
}
leftLine , rightLine int
2014-09-17 12:03:03 +08:00
isTooLong bool
2014-04-13 09:35:36 +08:00
)
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
}
2014-09-17 12:03:03 +08:00
if line == "" {
continue
}
2014-04-13 09:35:36 +08:00
i = i + 1
2014-09-17 12:03:03 +08:00
// Diff data too large, we only show the first about maxlines lines
if i == maxlines {
isTooLong = true
2014-04-13 09:35:36 +08:00
log . Warn ( "Diff data too large" )
2014-09-17 12:03:03 +08:00
//return &Diff{}, nil
2014-04-13 09:35:36 +08:00
}
2014-04-15 20:01:20 -04:00
switch {
case line [ 0 ] == ' ' :
2014-04-13 09:35:36 +08:00
diffLine := & DiffLine { Type : DIFF_LINE_PLAIN , Content : line , LeftIdx : leftLine , RightIdx : rightLine }
leftLine ++
rightLine ++
curSection . Lines = append ( curSection . Lines , diffLine )
continue
2014-04-15 20:01:20 -04:00
case line [ 0 ] == '@' :
2014-09-17 12:03:03 +08:00
if isTooLong {
return diff , nil
}
2014-04-13 09:35:36 +08: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 : ] , " " )
2014-07-26 00:24:27 -04:00
leftLine , _ = com . StrTo ( strings . Split ( ranges [ 0 ] , "," ) [ 0 ] [ 1 : ] ) . Int ( )
rightLine , _ = com . StrTo ( strings . Split ( ranges [ 1 ] , "," ) [ 0 ] ) . Int ( )
2014-04-13 09:35:36 +08:00
continue
2014-04-15 20:01:20 -04:00
case line [ 0 ] == '+' :
2014-04-13 09:35:36 +08: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-15 20:01:20 -04:00
case line [ 0 ] == '-' :
2014-04-13 09:35:36 +08: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-15 20:01:20 -04:00
case strings . HasPrefix ( line , "Binary" ) :
curFile . IsBin = true
2014-04-13 09:35:36 +08:00
continue
}
// Get new file.
if strings . HasPrefix ( line , DIFF_HEAD ) {
2014-09-17 12:03:03 +08:00
if isTooLong {
return diff , nil
}
2014-04-13 09:35:36 +08:00
fs := strings . Split ( line [ len ( DIFF_HEAD ) : ] , " " )
a := fs [ 0 ]
curFile = & DiffFile {
Name : a [ strings . Index ( a , "/" ) + 1 : ] ,
2014-05-13 12:40:32 -04:00
Index : len ( diff . Files ) + 1 ,
2014-04-13 09:35:36 +08:00
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
}
2014-09-17 12:03:03 +08:00
func GetDiffRange ( repoPath , beforeCommitId string , afterCommitId string , maxlines int ) ( * Diff , error ) {
2014-04-13 09:35:36 +08:00
repo , err := git . OpenRepository ( repoPath )
if err != nil {
return nil , err
}
2014-08-26 08:20:18 -04:00
commit , err := repo . GetCommit ( afterCommitId )
2014-04-13 09:35:36 +08:00
if err != nil {
return nil , err
}
2014-05-28 22:15:15 -04:00
rd , wr := io . Pipe ( )
var cmd * exec . Cmd
2014-08-26 08:20:18 -04:00
// if "after" commit given
if beforeCommitId == "" {
// First commit of repository.
if commit . ParentCount ( ) == 0 {
cmd = exec . Command ( "git" , "show" , afterCommitId )
} else {
c , _ := commit . Parent ( 0 )
cmd = exec . Command ( "git" , "diff" , c . Id . String ( ) , afterCommitId )
}
2014-05-28 22:15:15 -04:00
} else {
2014-08-26 08:20:18 -04:00
cmd = exec . Command ( "git" , "diff" , beforeCommitId , afterCommitId )
2014-04-13 09:35:36 +08:00
}
2014-05-28 22:15:15 -04:00
cmd . Dir = repoPath
cmd . Stdout = wr
cmd . Stdin = os . Stdin
cmd . Stderr = os . Stderr
2014-07-06 17:32:36 -04:00
done := make ( chan error )
2014-04-13 09:35:36 +08:00
go func ( ) {
2014-07-06 17:32:36 -04:00
cmd . Start ( )
done <- cmd . Wait ( )
2014-04-13 09:35:36 +08:00
wr . Close ( )
} ( )
defer rd . Close ( )
2014-07-06 17:32:36 -04:00
2014-08-26 08:20:18 -04:00
desc := fmt . Sprintf ( "GetDiffRange(%s)" , repoPath )
2014-07-06 17:32:36 -04:00
pid := process . Add ( desc , cmd )
go func ( ) {
// In case process became zombie.
select {
case <- time . After ( 5 * time . Minute ) :
if errKill := process . Kill ( pid ) ; errKill != nil {
2014-07-26 00:24:27 -04:00
log . Error ( 4 , "git_diff.ParsePatch(Kill): %v" , err )
2014-07-06 17:32:36 -04:00
}
<- done
// return "", ErrExecTimeout.Error(), ErrExecTimeout
case err = <- done :
process . Remove ( pid )
}
} ( )
2014-09-17 12:03:03 +08:00
return ParsePatch ( pid , maxlines , cmd , rd )
2014-04-13 09:35:36 +08:00
}
2014-08-26 08:20:18 -04:00
2014-09-17 12:03:03 +08:00
func GetDiffCommit ( repoPath , commitId string , maxlines int ) ( * Diff , error ) {
return GetDiffRange ( repoPath , "" , commitId , maxlines )
2014-08-26 08:20:18 -04:00
}