2014-03-26 07:53:01 +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 (
2014-03-26 14:33:09 +04:00
"bufio"
2014-03-26 07:53:01 +04:00
"container/list"
"fmt"
2014-03-26 13:57:13 +04:00
"io"
"os"
"os/exec"
2014-03-26 14:33:09 +04:00
"path"
"strings"
2014-03-26 07:53:01 +04:00
"github.com/gogits/git"
2014-03-27 00:41:16 +04:00
"github.com/gogits/gogs/modules/base"
2014-03-26 07:53:01 +04:00
)
// RepoFile represents a file object in git repository.
type RepoFile struct {
* git . TreeEntry
Path string
Size int64
Repo * git . Repository
Commit * git . Commit
}
// LookupBlob returns the content of an object.
func ( file * RepoFile ) LookupBlob ( ) ( * git . Blob , error ) {
if file . Repo == nil {
return nil , ErrRepoFileNotLoaded
}
return file . Repo . LookupBlob ( file . Id )
}
// GetBranches returns all branches of given repository.
2014-03-27 20:07:22 +04:00
func GetBranches ( userName , repoName string ) ( [ ] string , error ) {
repo , err := git . OpenRepository ( RepoPath ( userName , repoName ) )
2014-03-26 07:53:01 +04:00
if err != nil {
return nil , err
}
refs , err := repo . AllReferences ( )
if err != nil {
return nil , err
}
brs := make ( [ ] string , len ( refs ) )
for i , ref := range refs {
2014-03-27 10:00:23 +04:00
brs [ i ] = ref . BranchName ( )
2014-03-26 07:53:01 +04:00
}
return brs , nil
}
2014-04-02 20:43:31 +04:00
// GetTags returns all tags of given repository.
func GetTags ( userName , repoName string ) ( [ ] string , error ) {
repo , err := git . OpenRepository ( RepoPath ( userName , repoName ) )
if err != nil {
return nil , err
}
refs , err := repo . AllTags ( )
if err != nil {
return nil , err
}
tags := make ( [ ] string , len ( refs ) )
for i , ref := range refs {
tags [ i ] = ref . Name
}
return tags , nil
}
2014-03-27 20:07:22 +04:00
func IsBranchExist ( userName , repoName , branchName string ) bool {
repo , err := git . OpenRepository ( RepoPath ( userName , repoName ) )
if err != nil {
return false
}
return repo . IsBranchExist ( branchName )
}
func GetTargetFile ( userName , repoName , branchName , commitId , rpath string ) ( * RepoFile , error ) {
repo , err := git . OpenRepository ( RepoPath ( userName , repoName ) )
2014-03-26 07:53:01 +04:00
if err != nil {
return nil , err
}
2014-03-30 06:13:02 +04:00
commit , err := repo . GetCommitOfBranch ( branchName )
2014-03-26 07:53:01 +04:00
if err != nil {
2014-03-30 06:13:02 +04:00
commit , err = repo . GetCommit ( commitId )
if err != nil {
return nil , err
}
2014-03-26 07:53:01 +04:00
}
parts := strings . Split ( path . Clean ( rpath ) , "/" )
var entry * git . TreeEntry
tree := commit . Tree
for i , part := range parts {
if i == len ( parts ) - 1 {
entry = tree . EntryByName ( part )
if entry == nil {
return nil , ErrRepoFileNotExist
}
} else {
tree , err = repo . SubTree ( tree , part )
if err != nil {
return nil , err
}
}
}
size , err := repo . ObjectSize ( entry . Id )
if err != nil {
return nil , err
}
repoFile := & RepoFile {
entry ,
rpath ,
size ,
repo ,
commit ,
}
return repoFile , nil
}
// GetReposFiles returns a list of file object in given directory of repository.
2014-03-30 06:13:02 +04:00
// func GetReposFilesOfBranch(userName, repoName, branchName, rpath string) ([]*RepoFile, error) {
// return getReposFiles(userName, repoName, commitId, rpath)
// }
// GetReposFiles returns a list of file object in given directory of repository.
func GetReposFiles ( userName , repoName , commitId , rpath string ) ( [ ] * RepoFile , error ) {
return getReposFiles ( userName , repoName , commitId , rpath )
}
func getReposFiles ( userName , repoName , commitId string , rpath string ) ( [ ] * RepoFile , error ) {
2014-03-27 20:07:22 +04:00
repo , err := git . OpenRepository ( RepoPath ( userName , repoName ) )
2014-03-26 07:53:01 +04:00
if err != nil {
return nil , err
}
2014-03-30 06:13:02 +04:00
commit , err := repo . GetCommit ( commitId )
2014-03-26 07:53:01 +04:00
if err != nil {
return nil , err
}
var repodirs [ ] * RepoFile
var repofiles [ ] * RepoFile
commit . Tree . Walk ( func ( dirname string , entry * git . TreeEntry ) int {
if dirname == rpath {
// TODO: size get method shoule be improved
size , err := repo . ObjectSize ( entry . Id )
if err != nil {
return 0
}
var cm = commit
var i int
for {
i = i + 1
//fmt.Println(".....", i, cm.Id(), cm.ParentCount())
if cm . ParentCount ( ) == 0 {
break
} else if cm . ParentCount ( ) == 1 {
pt , _ := repo . SubTree ( cm . Parent ( 0 ) . Tree , dirname )
if pt == nil {
break
}
pEntry := pt . EntryByName ( entry . Name )
if pEntry == nil || ! pEntry . Id . Equal ( entry . Id ) {
break
} else {
cm = cm . Parent ( 0 )
}
} else {
var emptyCnt = 0
var sameIdcnt = 0
var lastSameCm * git . Commit
//fmt.Println(".....", cm.ParentCount())
for i := 0 ; i < cm . ParentCount ( ) ; i ++ {
//fmt.Println("parent", i, cm.Parent(i).Id())
p := cm . Parent ( i )
pt , _ := repo . SubTree ( p . Tree , dirname )
var pEntry * git . TreeEntry
if pt != nil {
pEntry = pt . EntryByName ( entry . Name )
}
//fmt.Println("pEntry", pEntry)
if pEntry == nil {
emptyCnt = emptyCnt + 1
if emptyCnt + sameIdcnt == cm . ParentCount ( ) {
if lastSameCm == nil {
goto loop
} else {
cm = lastSameCm
break
}
}
} else {
//fmt.Println(i, "pEntry", pEntry.Id, "entry", entry.Id)
if ! pEntry . Id . Equal ( entry . Id ) {
goto loop
} else {
lastSameCm = cm . Parent ( i )
sameIdcnt = sameIdcnt + 1
if emptyCnt + sameIdcnt == cm . ParentCount ( ) {
// TODO: now follow the first parent commit?
cm = lastSameCm
//fmt.Println("sameId...")
break
}
}
}
}
}
}
loop :
rp := & RepoFile {
entry ,
path . Join ( dirname , entry . Name ) ,
size ,
repo ,
cm ,
}
if entry . IsFile ( ) {
repofiles = append ( repofiles , rp )
} else if entry . IsDir ( ) {
repodirs = append ( repodirs , rp )
}
}
return 0
} )
return append ( repodirs , repofiles ... ) , nil
}
2014-03-30 06:13:02 +04:00
func GetCommit ( userName , repoName , commitId string ) ( * git . Commit , error ) {
2014-03-26 07:53:01 +04:00
repo , err := git . OpenRepository ( RepoPath ( userName , repoName ) )
if err != nil {
return nil , err
}
2014-03-30 06:13:02 +04:00
return repo . GetCommit ( commitId )
2014-03-26 07:53:01 +04:00
}
2014-03-27 20:07:22 +04:00
// GetCommitsByBranch returns all commits of given branch of repository.
func GetCommitsByBranch ( userName , repoName , branchName string ) ( * list . List , error ) {
repo , err := git . OpenRepository ( RepoPath ( userName , repoName ) )
if err != nil {
return nil , err
}
r , err := repo . LookupReference ( fmt . Sprintf ( "refs/heads/%s" , branchName ) )
if err != nil {
return nil , err
}
return r . AllCommits ( )
}
// GetCommitsByCommitId returns all commits of given commitId of repository.
func GetCommitsByCommitId ( userName , repoName , commitId string ) ( * list . List , error ) {
repo , err := git . OpenRepository ( RepoPath ( userName , repoName ) )
2014-03-26 07:53:01 +04:00
if err != nil {
return nil , err
}
2014-03-28 15:34:01 +04:00
oid , err := git . NewOidFromString ( commitId )
2014-03-26 07:53:01 +04:00
if err != nil {
return nil , err
}
2014-03-28 15:34:01 +04:00
return repo . CommitsBefore ( oid )
2014-03-26 07:53:01 +04:00
}
2014-03-26 14:33:09 +04:00
// Diff line types.
2014-03-26 13:57:13 +04:00
const (
2014-03-26 14:33:09 +04:00
DIFF_LINE_PLAIN = iota + 1
DIFF_LINE_ADD
DIFF_LINE_DEL
DIFF_LINE_SECTION
2014-03-26 13:57:13 +04:00
)
const (
2014-03-26 14:33:09 +04:00
DIFF_FILE_ADD = iota + 1
DIFF_FILE_CHANGE
DIFF_FILE_DEL
2014-03-26 13:57:13 +04:00
)
type DiffLine struct {
2014-03-26 14:33:09 +04:00
LeftIdx int
2014-03-26 13:57:13 +04:00
RightIdx int
2014-03-26 14:33:09 +04:00
Type int
Content string
2014-03-26 13:57:13 +04:00
}
2014-03-27 00:41:16 +04:00
func ( d DiffLine ) GetType ( ) int {
return d . Type
}
2014-03-26 13:57:13 +04:00
type DiffSection struct {
2014-03-26 14:33:09 +04:00
Name string
2014-03-26 13:57:13 +04:00
Lines [ ] * DiffLine
}
2014-03-26 07:53:01 +04:00
type DiffFile struct {
Name string
Addition , Deletion int
2014-03-26 13:57:13 +04:00
Type int
2014-03-26 14:33:09 +04:00
Sections [ ] * DiffSection
2014-03-26 07:53:01 +04:00
}
type Diff struct {
TotalAddition , TotalDeletion int
Files [ ] * DiffFile
}
2014-03-26 13:57:13 +04:00
func ( diff * Diff ) NumFiles ( ) int {
return len ( diff . Files )
}
2014-03-26 14:33:09 +04:00
const DIFF_HEAD = "diff --git "
2014-03-26 13:57:13 +04:00
func ParsePatch ( reader io . Reader ) ( * Diff , error ) {
scanner := bufio . NewScanner ( reader )
2014-03-27 00:41:16 +04:00
var (
curFile * DiffFile
curSection = & DiffSection {
Lines : make ( [ ] * DiffLine , 0 , 10 ) ,
}
leftLine , rightLine int
)
2014-03-26 14:33:09 +04:00
diff := & Diff { Files : make ( [ ] * DiffFile , 0 ) }
2014-03-26 13:57:13 +04:00
var i int
for scanner . Scan ( ) {
line := scanner . Text ( )
2014-03-27 00:41:16 +04:00
// fmt.Println(i, line)
2014-03-26 15:24:20 +04:00
if strings . HasPrefix ( line , "+++ " ) || strings . HasPrefix ( line , "--- " ) {
continue
}
2014-03-26 13:57:13 +04:00
i = i + 1
if line == "" {
continue
}
if line [ 0 ] == ' ' {
2014-03-27 00:41:16 +04:00
diffLine := & DiffLine { Type : DIFF_LINE_PLAIN , Content : line , LeftIdx : leftLine , RightIdx : rightLine }
leftLine ++
rightLine ++
2014-03-26 13:57:13 +04:00
curSection . Lines = append ( curSection . Lines , diffLine )
continue
} else if line [ 0 ] == '@' {
2014-03-26 14:02:08 +04:00
curSection = & DiffSection { }
curFile . Sections = append ( curFile . Sections , curSection )
2014-03-26 13:57:13 +04:00
ss := strings . Split ( line , "@@" )
2014-03-27 00:41:16 +04:00
diffLine := & DiffLine { Type : DIFF_LINE_SECTION , Content : line }
2014-03-26 13:57:13 +04:00
curSection . Lines = append ( curSection . Lines , diffLine )
2014-03-27 00:41:16 +04:00
// 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 ( )
2014-03-26 13:57:13 +04:00
continue
} else if line [ 0 ] == '+' {
2014-03-26 15:24:20 +04:00
curFile . Addition ++
diff . TotalAddition ++
2014-03-27 00:41:16 +04:00
diffLine := & DiffLine { Type : DIFF_LINE_ADD , Content : line , RightIdx : rightLine }
rightLine ++
2014-03-26 13:57:13 +04:00
curSection . Lines = append ( curSection . Lines , diffLine )
continue
} else if line [ 0 ] == '-' {
2014-03-26 15:24:20 +04:00
curFile . Deletion ++
diff . TotalDeletion ++
2014-03-27 00:41:16 +04:00
diffLine := & DiffLine { Type : DIFF_LINE_DEL , Content : line , LeftIdx : leftLine }
if leftLine > 0 {
leftLine ++
}
2014-03-26 13:57:13 +04:00
curSection . Lines = append ( curSection . Lines , diffLine )
continue
}
2014-03-26 15:24:20 +04:00
// Get new file.
2014-03-26 14:33:09 +04:00
if strings . HasPrefix ( line , DIFF_HEAD ) {
fs := strings . Split ( line [ len ( DIFF_HEAD ) : ] , " " )
2014-03-26 13:57:13 +04:00
a := fs [ 0 ]
2014-03-26 14:33:09 +04:00
2014-03-26 13:57:13 +04:00
curFile = & DiffFile {
2014-03-26 14:33:09 +04:00
Name : a [ strings . Index ( a , "/" ) + 1 : ] ,
Type : DIFF_FILE_CHANGE ,
2014-03-26 15:24:20 +04:00
Sections : make ( [ ] * DiffSection , 0 , 10 ) ,
2014-03-26 13:57:13 +04:00
}
diff . Files = append ( diff . Files , curFile )
2014-03-26 15:24:20 +04:00
// 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
}
2014-03-26 13:57:13 +04:00
}
}
}
return diff , nil
}
2014-03-26 07:53:01 +04:00
func GetDiff ( repoPath , commitid string ) ( * Diff , error ) {
2014-03-26 13:57:13 +04:00
repo , err := git . OpenRepository ( repoPath )
if err != nil {
return nil , err
}
2014-03-30 06:13:02 +04:00
commit , err := repo . GetCommit ( commitid )
2014-03-26 13:57:13 +04:00
if err != nil {
return nil , err
}
2014-03-27 00:41:16 +04:00
// First commit of repository.
2014-03-26 13:57:13 +04:00
if commit . ParentCount ( ) == 0 {
2014-03-27 00:41:16 +04:00
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 )
2014-03-26 13:57:13 +04:00
}
rd , wr := io . Pipe ( )
go func ( ) {
2014-03-26 15:24:20 +04:00
cmd := exec . Command ( "git" , "diff" , commit . Parent ( 0 ) . Oid . String ( ) , commitid )
2014-03-26 13:57:13 +04:00
cmd . Dir = repoPath
cmd . Stdout = wr
cmd . Stdin = os . Stdin
cmd . Stderr = os . Stderr
cmd . Run ( )
wr . Close ( )
} ( )
defer rd . Close ( )
return ParsePatch ( rd )
}