2016-11-04 01:16:01 +03:00
// Copyright 2015 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 git
import (
"fmt"
"path"
"path/filepath"
2016-11-15 18:24:08 +03:00
"runtime"
2016-11-04 01:16:01 +03:00
"sort"
"strconv"
"strings"
)
2016-12-22 12:30:52 +03:00
// EntryMode the type of the object in the git tree
2016-11-04 01:16:01 +03:00
type EntryMode int
// There are only a few file modes in Git. They look like unix file modes, but they can only be
// one of these.
const (
2016-12-22 12:30:52 +03:00
// EntryModeBlob
EntryModeBlob EntryMode = 0100644
// EntryModeExec
EntryModeExec EntryMode = 0100755
// EntryModeSymlink
EntryModeSymlink EntryMode = 0120000
// EntryModeCommit
EntryModeCommit EntryMode = 0160000
// EntryModeTree
EntryModeTree EntryMode = 0040000
2016-11-04 01:16:01 +03:00
)
2016-12-22 12:30:52 +03:00
// TreeEntry the leaf in the git tree
2016-11-04 01:16:01 +03:00
type TreeEntry struct {
2016-12-22 12:30:52 +03:00
ID SHA1
2016-11-04 01:16:01 +03:00
Type ObjectType
mode EntryMode
name string
ptree * Tree
commited bool
size int64
sized bool
}
2016-12-22 12:30:52 +03:00
// Name returns the name of the entry
2016-11-04 01:16:01 +03:00
func ( te * TreeEntry ) Name ( ) string {
return te . name
}
2016-12-22 12:30:52 +03:00
// Size returns the size of the entry
2016-11-04 01:16:01 +03:00
func ( te * TreeEntry ) Size ( ) int64 {
if te . IsDir ( ) {
return 0
} else if te . sized {
return te . size
}
stdout , err := NewCommand ( "cat-file" , "-s" , te . ID . String ( ) ) . RunInDir ( te . ptree . repo . Path )
if err != nil {
return 0
}
te . sized = true
te . size , _ = strconv . ParseInt ( strings . TrimSpace ( stdout ) , 10 , 64 )
return te . size
}
2016-12-22 12:30:52 +03:00
// IsSubModule if the entry is a sub module
2016-11-04 01:16:01 +03:00
func ( te * TreeEntry ) IsSubModule ( ) bool {
2016-12-22 12:30:52 +03:00
return te . mode == EntryModeCommit
2016-11-04 01:16:01 +03:00
}
2016-12-22 12:30:52 +03:00
// IsDir if the entry is a sub dir
2016-11-04 01:16:01 +03:00
func ( te * TreeEntry ) IsDir ( ) bool {
2016-12-22 12:30:52 +03:00
return te . mode == EntryModeTree
2016-11-04 01:16:01 +03:00
}
2016-12-22 12:30:52 +03:00
// IsLink if the entry is a symlink
func ( te * TreeEntry ) IsLink ( ) bool {
return te . mode == EntryModeSymlink
}
// Blob retrun the blob object the entry
2016-11-04 01:16:01 +03:00
func ( te * TreeEntry ) Blob ( ) * Blob {
return & Blob {
repo : te . ptree . repo ,
TreeEntry : te ,
}
}
2016-12-22 12:30:52 +03:00
// Entries a list of entry
2016-11-04 01:16:01 +03:00
type Entries [ ] * TreeEntry
var sorter = [ ] func ( t1 , t2 * TreeEntry ) bool {
func ( t1 , t2 * TreeEntry ) bool {
return ( t1 . IsDir ( ) || t1 . IsSubModule ( ) ) && ! t2 . IsDir ( ) && ! t2 . IsSubModule ( )
} ,
func ( t1 , t2 * TreeEntry ) bool {
return t1 . name < t2 . name
} ,
}
func ( tes Entries ) Len ( ) int { return len ( tes ) }
func ( tes Entries ) Swap ( i , j int ) { tes [ i ] , tes [ j ] = tes [ j ] , tes [ i ] }
func ( tes Entries ) Less ( i , j int ) bool {
t1 , t2 := tes [ i ] , tes [ j ]
var k int
for k = 0 ; k < len ( sorter ) - 1 ; k ++ {
2016-11-12 14:09:25 +03:00
s := sorter [ k ]
2016-11-04 01:16:01 +03:00
switch {
2016-11-12 14:09:25 +03:00
case s ( t1 , t2 ) :
2016-11-04 01:16:01 +03:00
return true
2016-11-12 14:09:25 +03:00
case s ( t2 , t1 ) :
2016-11-04 01:16:01 +03:00
return false
}
}
return sorter [ k ] ( t1 , t2 )
}
2016-12-22 12:30:52 +03:00
// Sort sort the list of entry
2016-11-04 01:16:01 +03:00
func ( tes Entries ) Sort ( ) {
sort . Sort ( tes )
}
type commitInfo struct {
entryName string
infos [ ] interface { }
err error
}
2016-11-15 18:24:08 +03:00
// GetCommitsInfo takes advantages of concurrency to speed up getting information
// of all commits that are corresponding to these entries. This method will automatically
// choose the right number of goroutine (concurrency) to use related of the host CPU.
2016-11-04 01:16:01 +03:00
func ( tes Entries ) GetCommitsInfo ( commit * Commit , treePath string ) ( [ ] [ ] interface { } , error ) {
2016-11-15 18:24:08 +03:00
return tes . GetCommitsInfoWithCustomConcurrency ( commit , treePath , 0 )
}
// GetCommitsInfoWithCustomConcurrency takes advantages of concurrency to speed up getting information
// of all commits that are corresponding to these entries. If the given maxConcurrency is negative or
// equal to zero: the right number of goroutine (concurrency) to use will be choosen related of the
// host CPU.
func ( tes Entries ) GetCommitsInfoWithCustomConcurrency ( commit * Commit , treePath string , maxConcurrency int ) ( [ ] [ ] interface { } , error ) {
2016-11-04 01:16:01 +03:00
if len ( tes ) == 0 {
return nil , nil
}
2016-11-15 18:24:08 +03:00
if maxConcurrency <= 0 {
maxConcurrency = runtime . NumCPU ( )
}
2016-11-04 01:16:01 +03:00
// Length of taskChan determines how many goroutines (subprocesses) can run at the same time.
// The length of revChan should be same as taskChan so goroutines whoever finished job can
// exit as early as possible, only store data inside channel.
2016-11-15 18:24:08 +03:00
taskChan := make ( chan bool , maxConcurrency )
revChan := make ( chan commitInfo , maxConcurrency )
2016-11-04 01:16:01 +03:00
doneChan := make ( chan error )
// Receive loop will exit when it collects same number of data pieces as tree entries.
// It notifies doneChan before exits or notify early with possible error.
infoMap := make ( map [ string ] [ ] interface { } , len ( tes ) )
go func ( ) {
i := 0
for info := range revChan {
if info . err != nil {
doneChan <- info . err
return
}
infoMap [ info . entryName ] = info . infos
i ++
if i == len ( tes ) {
break
}
}
doneChan <- nil
} ( )
for i := range tes {
// When taskChan is idle (or has empty slots), put operation will not block.
// However when taskChan is full, code will block and wait any running goroutines to finish.
taskChan <- true
2016-12-22 12:30:52 +03:00
if tes [ i ] . Type != ObjectCommit {
2016-11-04 01:16:01 +03:00
go func ( i int ) {
cinfo := commitInfo { entryName : tes [ i ] . Name ( ) }
c , err := commit . GetCommitByPath ( filepath . Join ( treePath , tes [ i ] . Name ( ) ) )
if err != nil {
cinfo . err = fmt . Errorf ( "GetCommitByPath (%s/%s): %v" , treePath , tes [ i ] . Name ( ) , err )
} else {
cinfo . infos = [ ] interface { } { tes [ i ] , c }
}
revChan <- cinfo
<- taskChan // Clear one slot from taskChan to allow new goroutines to start.
} ( i )
continue
}
// Handle submodule
go func ( i int ) {
cinfo := commitInfo { entryName : tes [ i ] . Name ( ) }
sm , err := commit . GetSubModule ( path . Join ( treePath , tes [ i ] . Name ( ) ) )
if err != nil && ! IsErrNotExist ( err ) {
cinfo . err = fmt . Errorf ( "GetSubModule (%s/%s): %v" , treePath , tes [ i ] . Name ( ) , err )
revChan <- cinfo
return
}
smURL := ""
if sm != nil {
smURL = sm . URL
}
c , err := commit . GetCommitByPath ( filepath . Join ( treePath , tes [ i ] . Name ( ) ) )
if err != nil {
cinfo . err = fmt . Errorf ( "GetCommitByPath (%s/%s): %v" , treePath , tes [ i ] . Name ( ) , err )
} else {
cinfo . infos = [ ] interface { } { tes [ i ] , NewSubModuleFile ( c , smURL , tes [ i ] . ID . String ( ) ) }
}
revChan <- cinfo
<- taskChan
} ( i )
}
if err := <- doneChan ; err != nil {
return nil , err
}
commitsInfo := make ( [ ] [ ] interface { } , len ( tes ) )
for i := 0 ; i < len ( tes ) ; i ++ {
commitsInfo [ i ] = infoMap [ tes [ i ] . Name ( ) ]
}
return commitsInfo , nil
}