2016-11-04 01:16:01 +03:00
// Copyright 2015 The Gogs Authors. All rights reserved.
2019-04-19 15:17:27 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2016-11-04 01:16:01 +03:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"bytes"
"container/list"
2019-04-19 15:17:27 +03:00
"fmt"
2016-11-04 01:16:01 +03:00
"strconv"
"strings"
2018-05-27 21:47:34 +03:00
2019-04-17 19:06:35 +03:00
"github.com/mcuadros/go-version"
2019-04-19 15:17:27 +03:00
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/object"
2016-11-04 01:16:01 +03:00
)
2018-01-19 09:18:51 +03:00
// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
func ( repo * Repository ) GetRefCommitID ( name string ) ( string , error ) {
2019-04-19 15:17:27 +03:00
ref , err := repo . gogitRepo . Reference ( plumbing . ReferenceName ( name ) , true )
2016-11-04 01:16:01 +03:00
if err != nil {
return "" , err
}
2019-04-19 15:17:27 +03:00
return ref . Hash ( ) . String ( ) , nil
2016-11-04 01:16:01 +03:00
}
2019-06-07 23:29:29 +03:00
// IsCommitExist returns true if given commit exists in current repository.
func ( repo * Repository ) IsCommitExist ( name string ) bool {
hash := plumbing . NewHash ( name )
_ , err := repo . gogitRepo . CommitObject ( hash )
2019-06-12 22:41:28 +03:00
return err == nil
2019-06-07 23:29:29 +03:00
}
2016-11-04 01:16:01 +03:00
// GetBranchCommitID returns last commit ID string of given branch.
func ( repo * Repository ) GetBranchCommitID ( name string ) ( string , error ) {
2018-01-19 09:18:51 +03:00
return repo . GetRefCommitID ( BranchPrefix + name )
2016-11-04 01:16:01 +03:00
}
// GetTagCommitID returns last commit ID string of given tag.
func ( repo * Repository ) GetTagCommitID ( name string ) ( string , error ) {
2019-03-11 06:44:58 +03:00
stdout , err := NewCommand ( "rev-list" , "-n" , "1" , name ) . RunInDir ( repo . Path )
if err != nil {
if strings . Contains ( err . Error ( ) , "unknown revision or path" ) {
return "" , ErrNotExist { name , "" }
}
return "" , err
}
return strings . TrimSpace ( stdout ) , nil
2016-11-04 01:16:01 +03:00
}
2019-04-19 15:17:27 +03:00
func convertPGPSignatureForTag ( t * object . Tag ) * CommitGPGSignature {
if t . PGPSignature == "" {
return nil
}
2018-09-07 05:06:09 +03:00
2019-04-19 15:17:27 +03:00
var w strings . Builder
var err error
if _ , err = fmt . Fprintf ( & w ,
"object %s\ntype %s\ntag %s\ntagger " ,
t . Target . String ( ) , t . TargetType . Bytes ( ) , t . Name ) ; err != nil {
return nil
}
if err = t . Tagger . Encode ( & w ) ; err != nil {
return nil
}
if _ , err = fmt . Fprintf ( & w , "\n\n" ) ; err != nil {
return nil
}
if _ , err = fmt . Fprintf ( & w , t . Message ) ; err != nil {
return nil
}
return & CommitGPGSignature {
Signature : t . PGPSignature ,
Payload : strings . TrimSpace ( w . String ( ) ) + "\n" ,
2016-11-04 01:16:01 +03:00
}
}
2016-12-22 12:30:52 +03:00
func ( repo * Repository ) getCommit ( id SHA1 ) ( * Commit , error ) {
2019-04-19 15:17:27 +03:00
var tagObject * object . Tag
2016-11-04 01:16:01 +03:00
2019-04-19 15:17:27 +03:00
gogitCommit , err := repo . gogitRepo . CommitObject ( plumbing . Hash ( id ) )
if err == plumbing . ErrObjectNotFound {
tagObject , err = repo . gogitRepo . TagObject ( plumbing . Hash ( id ) )
if err == nil {
gogitCommit , err = repo . gogitRepo . CommitObject ( tagObject . Target )
2016-11-04 01:16:01 +03:00
}
}
if err != nil {
return nil , err
}
2019-04-19 15:17:27 +03:00
commit := convertCommit ( gogitCommit )
2016-11-04 01:16:01 +03:00
commit . repo = repo
2019-04-19 15:17:27 +03:00
if tagObject != nil {
commit . CommitMessage = strings . TrimSpace ( tagObject . Message )
commit . Author = & tagObject . Tagger
commit . Signature = convertPGPSignatureForTag ( tagObject )
}
tree , err := gogitCommit . Tree ( )
2019-02-06 00:47:01 +03:00
if err != nil {
return nil , err
}
2019-04-19 15:17:27 +03:00
commit . Tree . ID = tree . Hash
commit . Tree . gogitTree = tree
2019-02-06 00:47:01 +03:00
2016-11-04 01:16:01 +03:00
return commit , nil
}
// GetCommit returns commit object of by ID string.
func ( repo * Repository ) GetCommit ( commitID string ) ( * Commit , error ) {
if len ( commitID ) != 40 {
var err error
2019-03-11 21:01:00 +03:00
actualCommitID , err := NewCommand ( "rev-parse" , commitID ) . RunInDir ( repo . Path )
2016-11-04 01:16:01 +03:00
if err != nil {
2019-02-03 06:35:17 +03:00
if strings . Contains ( err . Error ( ) , "unknown revision or path" ) {
return nil , ErrNotExist { commitID , "" }
}
2016-11-04 01:16:01 +03:00
return nil , err
}
2019-03-11 21:01:00 +03:00
commitID = actualCommitID
2016-11-04 01:16:01 +03:00
}
id , err := NewIDFromString ( commitID )
if err != nil {
return nil , err
}
return repo . getCommit ( id )
}
// GetBranchCommit returns the last commit of given branch.
func ( repo * Repository ) GetBranchCommit ( name string ) ( * Commit , error ) {
commitID , err := repo . GetBranchCommitID ( name )
if err != nil {
return nil , err
}
return repo . GetCommit ( commitID )
}
2016-12-22 12:30:52 +03:00
// GetTagCommit get the commit of the specific tag via name
2016-11-04 01:16:01 +03:00
func ( repo * Repository ) GetTagCommit ( name string ) ( * Commit , error ) {
commitID , err := repo . GetTagCommitID ( name )
if err != nil {
return nil , err
}
return repo . GetCommit ( commitID )
}
2016-12-22 12:30:52 +03:00
func ( repo * Repository ) getCommitByPathWithID ( id SHA1 , relpath string ) ( * Commit , error ) {
2016-11-04 01:16:01 +03:00
// File name starts with ':' must be escaped.
if relpath [ 0 ] == ':' {
relpath = ` \ ` + relpath
}
2016-12-22 12:30:52 +03:00
stdout , err := NewCommand ( "log" , "-1" , prettyLogFormat , id . String ( ) , "--" , relpath ) . RunInDir ( repo . Path )
2016-11-04 01:16:01 +03:00
if err != nil {
return nil , err
}
id , err = NewIDFromString ( stdout )
if err != nil {
return nil , err
}
return repo . getCommit ( id )
}
// GetCommitByPath returns the last commit of relative path.
func ( repo * Repository ) GetCommitByPath ( relpath string ) ( * Commit , error ) {
2016-12-22 12:30:52 +03:00
stdout , err := NewCommand ( "log" , "-1" , prettyLogFormat , "--" , relpath ) . RunInDirBytes ( repo . Path )
2016-11-04 01:16:01 +03:00
if err != nil {
return nil , err
}
commits , err := repo . parsePrettyFormatLogToList ( stdout )
if err != nil {
return nil , err
}
return commits . Front ( ) . Value . ( * Commit ) , nil
}
2016-12-22 12:30:52 +03:00
// CommitsRangeSize the default commits range size
2016-11-04 01:16:01 +03:00
var CommitsRangeSize = 50
2016-12-22 12:30:52 +03:00
func ( repo * Repository ) commitsByRange ( id SHA1 , page int ) ( * list . List , error ) {
2016-11-04 01:16:01 +03:00
stdout , err := NewCommand ( "log" , id . String ( ) , "--skip=" + strconv . Itoa ( ( page - 1 ) * CommitsRangeSize ) ,
2016-12-22 12:30:52 +03:00
"--max-count=" + strconv . Itoa ( CommitsRangeSize ) , prettyLogFormat ) . RunInDirBytes ( repo . Path )
2016-11-04 01:16:01 +03:00
if err != nil {
return nil , err
}
return repo . parsePrettyFormatLogToList ( stdout )
}
2019-04-12 05:28:44 +03:00
func ( repo * Repository ) searchCommits ( id SHA1 , opts SearchCommitsOptions ) ( * list . List , error ) {
cmd := NewCommand ( "log" , id . String ( ) , "-100" , "-i" , prettyLogFormat )
if len ( opts . Keywords ) > 0 {
for _ , v := range opts . Keywords {
cmd . AddArguments ( "--grep=" + v )
}
}
if len ( opts . Authors ) > 0 {
for _ , v := range opts . Authors {
cmd . AddArguments ( "--author=" + v )
}
}
if len ( opts . Committers ) > 0 {
for _ , v := range opts . Committers {
cmd . AddArguments ( "--committer=" + v )
}
}
if len ( opts . After ) > 0 {
cmd . AddArguments ( "--after=" + opts . After )
}
if len ( opts . Before ) > 0 {
cmd . AddArguments ( "--before=" + opts . Before )
}
if opts . All {
2017-02-05 17:43:28 +03:00
cmd . AddArguments ( "--all" )
}
stdout , err := cmd . RunInDirBytes ( repo . Path )
2016-11-04 01:16:01 +03:00
if err != nil {
return nil , err
}
return repo . parsePrettyFormatLogToList ( stdout )
}
2019-04-17 19:06:35 +03:00
func ( repo * Repository ) getFilesChanged ( id1 , id2 string ) ( [ ] string , error ) {
2016-11-04 01:16:01 +03:00
stdout , err := NewCommand ( "diff" , "--name-only" , id1 , id2 ) . RunInDirBytes ( repo . Path )
if err != nil {
return nil , err
}
return strings . Split ( string ( stdout ) , "\n" ) , nil
}
2019-04-17 19:06:35 +03:00
// FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2
func ( repo * Repository ) FileChangedBetweenCommits ( filename , id1 , id2 string ) ( bool , error ) {
stdout , err := NewCommand ( "diff" , "--name-only" , "-z" , id1 , id2 , "--" , filename ) . RunInDirBytes ( repo . Path )
if err != nil {
return false , err
}
return len ( strings . TrimSpace ( string ( stdout ) ) ) > 0 , nil
}
2016-12-22 12:30:52 +03:00
// FileCommitsCount return the number of files at a revison
2016-11-04 01:16:01 +03:00
func ( repo * Repository ) FileCommitsCount ( revision , file string ) ( int64 , error ) {
return commitsCount ( repo . Path , revision , file )
}
2019-03-27 12:33:00 +03:00
// CommitsByFileAndRange return the commits according revison file and the page
2016-11-04 01:16:01 +03:00
func ( repo * Repository ) CommitsByFileAndRange ( revision , file string , page int ) ( * list . List , error ) {
2017-04-08 05:23:39 +03:00
stdout , err := NewCommand ( "log" , revision , "--follow" , "--skip=" + strconv . Itoa ( ( page - 1 ) * 50 ) ,
2016-12-22 12:30:52 +03:00
"--max-count=" + strconv . Itoa ( CommitsRangeSize ) , prettyLogFormat , "--" , file ) . RunInDirBytes ( repo . Path )
2016-11-04 01:16:01 +03:00
if err != nil {
return nil , err
}
return repo . parsePrettyFormatLogToList ( stdout )
}
2016-12-22 12:30:52 +03:00
// FilesCountBetween return the number of files changed between two commits
2016-11-04 01:16:01 +03:00
func ( repo * Repository ) FilesCountBetween ( startCommitID , endCommitID string ) ( int , error ) {
stdout , err := NewCommand ( "diff" , "--name-only" , startCommitID + "..." + endCommitID ) . RunInDir ( repo . Path )
if err != nil {
return 0 , err
}
return len ( strings . Split ( stdout , "\n" ) ) - 1 , nil
}
// CommitsBetween returns a list that contains commits between [last, before).
func ( repo * Repository ) CommitsBetween ( last * Commit , before * Commit ) ( * list . List , error ) {
2017-10-23 16:36:14 +03:00
stdout , err := NewCommand ( "rev-list" , before . ID . String ( ) + "..." + last . ID . String ( ) ) . RunInDirBytes ( repo . Path )
if err != nil {
return nil , err
2016-11-04 01:16:01 +03:00
}
2017-10-23 16:36:14 +03:00
return repo . parsePrettyFormatLogToList ( bytes . TrimSpace ( stdout ) )
2016-11-04 01:16:01 +03:00
}
2016-12-22 12:30:52 +03:00
// CommitsBetweenIDs return commits between twoe commits
2016-11-04 01:16:01 +03:00
func ( repo * Repository ) CommitsBetweenIDs ( last , before string ) ( * list . List , error ) {
lastCommit , err := repo . GetCommit ( last )
if err != nil {
return nil , err
}
beforeCommit , err := repo . GetCommit ( before )
if err != nil {
return nil , err
}
return repo . CommitsBetween ( lastCommit , beforeCommit )
}
2016-12-22 12:30:52 +03:00
// CommitsCountBetween return numbers of commits between two commits
2016-11-04 01:16:01 +03:00
func ( repo * Repository ) CommitsCountBetween ( start , end string ) ( int64 , error ) {
return commitsCount ( repo . Path , start + "..." + end , "" )
}
2016-12-22 12:30:52 +03:00
// commitsBefore the limit is depth, not total number of returned commits.
2017-12-11 05:23:34 +03:00
func ( repo * Repository ) commitsBefore ( id SHA1 , limit int ) ( * list . List , error ) {
cmd := NewCommand ( "log" )
if limit > 0 {
2018-05-27 21:47:34 +03:00
cmd . AddArguments ( "-" + strconv . Itoa ( limit ) , prettyLogFormat , id . String ( ) )
2017-12-11 05:23:34 +03:00
} else {
cmd . AddArguments ( prettyLogFormat , id . String ( ) )
2016-11-04 01:16:01 +03:00
}
2017-12-11 05:23:34 +03:00
stdout , err := cmd . RunInDirBytes ( repo . Path )
2016-11-04 01:16:01 +03:00
if err != nil {
2017-12-11 05:23:34 +03:00
return nil , err
2016-11-04 01:16:01 +03:00
}
2017-12-11 05:23:34 +03:00
formattedLog , err := repo . parsePrettyFormatLogToList ( bytes . TrimSpace ( stdout ) )
if err != nil {
return nil , err
2016-11-04 01:16:01 +03:00
}
2017-12-11 05:23:34 +03:00
commits := list . New ( )
for logEntry := formattedLog . Front ( ) ; logEntry != nil ; logEntry = logEntry . Next ( ) {
commit := logEntry . Value . ( * Commit )
branches , err := repo . getBranches ( commit , 2 )
2016-11-04 01:16:01 +03:00
if err != nil {
2017-12-11 05:23:34 +03:00
return nil , err
2016-11-04 01:16:01 +03:00
}
2017-12-11 05:23:34 +03:00
if len ( branches ) > 1 {
break
2016-11-04 01:16:01 +03:00
}
2017-12-11 05:23:34 +03:00
commits . PushBack ( commit )
2016-11-04 01:16:01 +03:00
}
2017-12-11 05:23:34 +03:00
return commits , nil
2016-11-04 01:16:01 +03:00
}
2016-12-22 12:30:52 +03:00
func ( repo * Repository ) getCommitsBefore ( id SHA1 ) ( * list . List , error ) {
2017-12-11 05:23:34 +03:00
return repo . commitsBefore ( id , 0 )
2016-11-04 01:16:01 +03:00
}
2016-12-22 12:30:52 +03:00
func ( repo * Repository ) getCommitsBeforeLimit ( id SHA1 , num int ) ( * list . List , error ) {
2017-12-11 05:23:34 +03:00
return repo . commitsBefore ( id , num )
}
func ( repo * Repository ) getBranches ( commit * Commit , limit int ) ( [ ] string , error ) {
2018-05-27 21:47:34 +03:00
if version . Compare ( gitVersion , "2.7.0" , ">=" ) {
stdout , err := NewCommand ( "for-each-ref" , "--count=" + strconv . Itoa ( limit ) , "--format=%(refname:strip=2)" , "--contains" , commit . ID . String ( ) , BranchPrefix ) . RunInDir ( repo . Path )
if err != nil {
return nil , err
}
branches := strings . Fields ( stdout )
return branches , nil
}
stdout , err := NewCommand ( "branch" , "--contains" , commit . ID . String ( ) ) . RunInDir ( repo . Path )
2017-12-11 05:23:34 +03:00
if err != nil {
return nil , err
}
refs := strings . Split ( stdout , "\n" )
2018-05-27 21:47:34 +03:00
var max int
if len ( refs ) > limit {
max = limit
} else {
max = len ( refs ) - 1
}
branches := make ( [ ] string , max )
for i , ref := range refs [ : max ] {
parts := strings . Fields ( ref )
branches [ i ] = parts [ len ( parts ) - 1 ]
2017-12-11 05:23:34 +03:00
}
return branches , nil
2016-11-04 01:16:01 +03:00
}