@ -6,6 +6,7 @@
package git
import (
"fmt"
"strings"
"github.com/mcuadros/go-version"
@ -35,34 +36,78 @@ func (repo *Repository) CreateTag(name, revision string) error {
return err
}
// CreateAnnotatedTag create one annotated tag in the repository
func ( repo * Repository ) CreateAnnotatedTag ( name , message , revision string ) error {
_ , err := NewCommand ( "tag" , "-a" , "-m" , message , name , revision ) . RunInDir ( repo . Path )
return err
}
func ( repo * Repository ) getTag ( id SHA1 ) ( * Tag , error ) {
t , ok := repo . tagCache . Get ( id . String ( ) )
if ok {
log ( "Hit cache: %s" , id )
return t . ( * Tag ) , nil
tagClone := * t . ( * Tag )
return & tagClone , nil
}
// Get tag type
tp , err := NewCommand ( "cat-file" , "-t" , id . String ( ) ) . RunInDir ( repo . Path )
// Get tag name
name , err := repo . GetTagNameBySHA ( id . String ( ) )
if err != nil {
return nil , err
}
tp , err := repo . GetTagType ( id )
if err != nil {
return nil , err
}
tp = strings . TrimSpace ( tp )
// Tag is a commit.
// Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object
commitIDStr , err := repo . GetTagCommitID ( name )
if err != nil {
// every tag should have a commit ID so return all errors
return nil , err
}
commitID , err := NewIDFromString ( commitIDStr )
if err != nil {
return nil , err
}
// tagID defaults to the commit ID as the tag ID and then tries to get a tag ID (only annotated tags)
tagID := commitID
if tagIDStr , err := repo . GetTagID ( name ) ; err != nil {
// if the err is NotExist then we can ignore and just keep tagID as ID (is lightweight tag)
// all other errors we return
if ! IsErrNotExist ( err ) {
return nil , err
}
} else {
tagID , err = NewIDFromString ( tagIDStr )
if err != nil {
return nil , err
}
}
// If type is "commit, the tag is a lightweight tag
if ObjectType ( tp ) == ObjectCommit {
commit , err := repo . GetCommit ( id . String ( ) )
if err != nil {
return nil , err
}
tag := & Tag {
ID : id ,
Object : id ,
Type : string ( ObjectCommit ) ,
repo : repo ,
Name : name ,
ID : tagID ,
Object : commitID ,
Type : string ( ObjectCommit ) ,
Tagger : commit . Committer ,
Message : commit . Message ( ) ,
repo : repo ,
}
repo . tagCache . Set ( id . String ( ) , tag )
return tag , nil
}
// Tag with message.
// The tag is an annotated t ag with a message.
data , err := NewCommand ( "cat-file" , "-p" , id . String ( ) ) . RunInDirBytes ( repo . Path )
if err != nil {
return nil , err
@ -73,16 +118,57 @@ func (repo *Repository) getTag(id SHA1) (*Tag, error) {
return nil , err
}
tag . Name = name
tag . ID = id
tag . repo = repo
tag . Type = tp
repo . tagCache . Set ( id . String ( ) , tag )
return tag , nil
}
// GetTagNameBySHA returns the name of a tag from its tag object SHA or commit SHA
func ( repo * Repository ) GetTagNameBySHA ( sha string ) ( string , error ) {
if len ( sha ) < 5 {
return "" , fmt . Errorf ( "SHA is too short: %s" , sha )
}
stdout , err := NewCommand ( "show-ref" , "--tags" , "-d" ) . RunInDir ( repo . Path )
if err != nil {
return "" , err
}
tagRefs := strings . Split ( stdout , "\n" )
for _ , tagRef := range tagRefs {
if len ( strings . TrimSpace ( tagRef ) ) > 0 {
fields := strings . Fields ( tagRef )
if strings . HasPrefix ( fields [ 0 ] , sha ) && strings . HasPrefix ( fields [ 1 ] , TagPrefix ) {
name := fields [ 1 ] [ len ( TagPrefix ) : ]
// annotated tags show up twice, their name for commit ID is suffixed with ^{}
name = strings . TrimSuffix ( name , "^{}" )
return name , nil
}
}
}
return "" , ErrNotExist { ID : sha }
}
// GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
func ( repo * Repository ) GetTagID ( name string ) ( string , error ) {
stdout , err := NewCommand ( "show-ref" , name ) . RunInDir ( repo . Path )
if err != nil {
return "" , err
}
fields := strings . Fields ( stdout )
if len ( fields ) != 2 {
return "" , ErrNotExist { ID : name }
}
return fields [ 0 ] , nil
}
// GetTag returns a Git tag by given name.
func ( repo * Repository ) GetTag ( name string ) ( * Tag , error ) {
idStr , err := repo . GetTagCommitID ( name )
idStr , err := repo . GetTagID ( name )
if err != nil {
return nil , err
}
@ -96,7 +182,6 @@ func (repo *Repository) GetTag(name string) (*Tag, error) {
if err != nil {
return nil , err
}
tag . Name = name
return tag , nil
}
@ -108,7 +193,7 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
return nil , err
}
tagNames := strings . Split ( stdout , "\n" )
tagNames := strings . Split ( strings . TrimRight ( st dout , "\n" ) , "\n" )
var tags = make ( [ ] * Tag , 0 , len ( tagNames ) )
for _ , tagName := range tagNames {
tagName = strings . TrimSpace ( tagName )
@ -120,6 +205,7 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) {
if err != nil {
return nil , err
}
tag . Name = tagName
tags = append ( tags , tag )
}
sortTagsByTime ( tags )
@ -150,3 +236,38 @@ func (repo *Repository) GetTags() ([]string, error) {
return tagNames , nil
}
// GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
func ( repo * Repository ) GetTagType ( id SHA1 ) ( string , error ) {
// Get tag type
stdout , err := NewCommand ( "cat-file" , "-t" , id . String ( ) ) . RunInDir ( repo . Path )
if err != nil {
return "" , err
}
if len ( stdout ) == 0 {
return "" , ErrNotExist { ID : id . String ( ) }
}
return strings . TrimSpace ( stdout ) , nil
}
// GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag
func ( repo * Repository ) GetAnnotatedTag ( sha string ) ( * Tag , error ) {
id , err := NewIDFromString ( sha )
if err != nil {
return nil , err
}
// Tag type must be "tag" (annotated) and not a "commit" (lightweight) tag
if tagType , err := repo . GetTagType ( id ) ; err != nil {
return nil , err
} else if ObjectType ( tagType ) != ObjectTag {
// not an annotated tag
return nil , ErrNotExist { ID : id . String ( ) }
}
tag , err := repo . getTag ( id )
if err != nil {
return nil , err
}
return tag , nil
}