2016-11-03 22:16:01 +00:00
|
|
|
// Copyright 2015 The Gogs Authors. All rights reserved.
|
2019-04-19 12:17:27 +00:00
|
|
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
2016-11-03 22:16:01 +00:00
|
|
|
// Use of this source code is governed by a MIT-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package git
|
|
|
|
|
|
|
|
import (
|
2019-06-08 14:31:11 +00:00
|
|
|
"fmt"
|
2016-11-03 22:16:01 +00:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/mcuadros/go-version"
|
2019-04-19 12:17:27 +00:00
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
2016-11-03 22:16:01 +00:00
|
|
|
)
|
|
|
|
|
2016-12-22 09:30:52 +00:00
|
|
|
// TagPrefix tags prefix path on the repository
|
|
|
|
const TagPrefix = "refs/tags/"
|
2016-11-03 22:16:01 +00:00
|
|
|
|
|
|
|
// IsTagExist returns true if given tag exists in the repository.
|
|
|
|
func IsTagExist(repoPath, name string) bool {
|
2016-12-22 09:30:52 +00:00
|
|
|
return IsReferenceExist(repoPath, TagPrefix+name)
|
2016-11-03 22:16:01 +00:00
|
|
|
}
|
|
|
|
|
2016-12-22 09:30:52 +00:00
|
|
|
// IsTagExist returns true if given tag exists in the repository.
|
2016-11-03 22:16:01 +00:00
|
|
|
func (repo *Repository) IsTagExist(name string) bool {
|
2019-04-19 12:17:27 +00:00
|
|
|
_, err := repo.gogitRepo.Reference(plumbing.ReferenceName(TagPrefix+name), true)
|
2019-06-12 19:41:28 +00:00
|
|
|
return err == nil
|
2016-11-03 22:16:01 +00:00
|
|
|
}
|
|
|
|
|
2016-12-22 09:30:52 +00:00
|
|
|
// CreateTag create one tag in the repository
|
2016-11-03 22:16:01 +00:00
|
|
|
func (repo *Repository) CreateTag(name, revision string) error {
|
|
|
|
_, err := NewCommand("tag", name, revision).RunInDir(repo.Path)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-06-08 14:31:11 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2016-12-22 09:30:52 +00:00
|
|
|
func (repo *Repository) getTag(id SHA1) (*Tag, error) {
|
2016-11-03 22:16:01 +00:00
|
|
|
t, ok := repo.tagCache.Get(id.String())
|
|
|
|
if ok {
|
|
|
|
log("Hit cache: %s", id)
|
2019-06-08 14:31:11 +00:00
|
|
|
tagClone := *t.(*Tag)
|
|
|
|
return &tagClone, nil
|
2016-11-03 22:16:01 +00:00
|
|
|
}
|
|
|
|
|
2019-06-08 14:31:11 +00:00
|
|
|
// Get tag name
|
|
|
|
name, err := repo.GetTagNameBySHA(id.String())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tp, err := repo.GetTagType(id)
|
2016-11-03 22:16:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-06-08 14:31:11 +00:00
|
|
|
// 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
|
2016-12-22 09:30:52 +00:00
|
|
|
if ObjectType(tp) == ObjectCommit {
|
2019-06-08 14:31:11 +00:00
|
|
|
commit, err := repo.GetCommit(id.String())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-11-03 22:16:01 +00:00
|
|
|
tag := &Tag{
|
2019-06-08 14:31:11 +00:00
|
|
|
Name: name,
|
|
|
|
ID: tagID,
|
|
|
|
Object: commitID,
|
|
|
|
Type: string(ObjectCommit),
|
|
|
|
Tagger: commit.Committer,
|
|
|
|
Message: commit.Message(),
|
|
|
|
repo: repo,
|
2016-11-03 22:16:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
repo.tagCache.Set(id.String(), tag)
|
|
|
|
return tag, nil
|
|
|
|
}
|
|
|
|
|
2019-06-08 14:31:11 +00:00
|
|
|
// The tag is an annotated tag with a message.
|
2016-11-03 22:16:01 +00:00
|
|
|
data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tag, err := parseTagData(data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-06-08 14:31:11 +00:00
|
|
|
tag.Name = name
|
2016-11-03 22:16:01 +00:00
|
|
|
tag.ID = id
|
|
|
|
tag.repo = repo
|
2019-06-08 14:31:11 +00:00
|
|
|
tag.Type = tp
|
2016-11-03 22:16:01 +00:00
|
|
|
|
|
|
|
repo.tagCache.Set(id.String(), tag)
|
|
|
|
return tag, nil
|
|
|
|
}
|
|
|
|
|
2019-06-08 14:31:11 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2016-11-03 22:16:01 +00:00
|
|
|
// GetTag returns a Git tag by given name.
|
|
|
|
func (repo *Repository) GetTag(name string) (*Tag, error) {
|
2019-06-08 14:31:11 +00:00
|
|
|
idStr, err := repo.GetTagID(name)
|
2016-11-03 22:16:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-03-11 03:44:58 +00:00
|
|
|
id, err := NewIDFromString(idStr)
|
2016-11-03 22:16:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tag, err := repo.getTag(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return tag, nil
|
|
|
|
}
|
|
|
|
|
2017-01-06 01:51:15 +00:00
|
|
|
// GetTagInfos returns all tag infos of the repository.
|
2017-06-06 17:36:48 +00:00
|
|
|
func (repo *Repository) GetTagInfos() ([]*Tag, error) {
|
|
|
|
// TODO this a slow implementation, makes one git command per tag
|
|
|
|
stdout, err := NewCommand("tag").RunInDir(repo.Path)
|
2017-01-06 01:51:15 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-06-08 14:31:11 +00:00
|
|
|
tagNames := strings.Split(strings.TrimRight(stdout, "\n"), "\n")
|
2019-02-07 12:00:52 +00:00
|
|
|
var tags = make([]*Tag, 0, len(tagNames))
|
2017-06-06 17:36:48 +00:00
|
|
|
for _, tagName := range tagNames {
|
|
|
|
tagName = strings.TrimSpace(tagName)
|
|
|
|
if len(tagName) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
2019-02-07 12:00:52 +00:00
|
|
|
|
|
|
|
tag, err := repo.GetTag(tagName)
|
2017-06-06 17:36:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-01-06 01:51:15 +00:00
|
|
|
}
|
2019-06-08 14:31:11 +00:00
|
|
|
tag.Name = tagName
|
2019-02-07 12:00:52 +00:00
|
|
|
tags = append(tags, tag)
|
2017-01-06 01:51:15 +00:00
|
|
|
}
|
|
|
|
sortTagsByTime(tags)
|
|
|
|
return tags, nil
|
|
|
|
}
|
|
|
|
|
2016-11-03 22:16:01 +00:00
|
|
|
// GetTags returns all tags of the repository.
|
|
|
|
func (repo *Repository) GetTags() ([]string, error) {
|
2019-04-19 12:17:27 +00:00
|
|
|
var tagNames []string
|
2016-11-03 22:16:01 +00:00
|
|
|
|
2019-04-19 12:17:27 +00:00
|
|
|
tags, err := repo.gogitRepo.Tags()
|
2016-11-03 22:16:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-06-12 19:41:28 +00:00
|
|
|
_ = tags.ForEach(func(tag *plumbing.Reference) error {
|
2019-04-19 12:17:27 +00:00
|
|
|
tagNames = append(tagNames, strings.TrimPrefix(tag.Name().String(), TagPrefix))
|
|
|
|
return nil
|
|
|
|
})
|
2016-11-03 22:16:01 +00:00
|
|
|
|
2019-04-19 12:17:27 +00:00
|
|
|
version.Sort(tagNames)
|
2016-11-03 22:16:01 +00:00
|
|
|
|
2019-04-19 12:17:27 +00:00
|
|
|
// Reverse order
|
|
|
|
for i := 0; i < len(tagNames)/2; i++ {
|
|
|
|
j := len(tagNames) - i - 1
|
|
|
|
tagNames[i], tagNames[j] = tagNames[j], tagNames[i]
|
2016-11-03 22:16:01 +00:00
|
|
|
}
|
|
|
|
|
2019-04-19 12:17:27 +00:00
|
|
|
return tagNames, nil
|
2016-11-03 22:16:01 +00:00
|
|
|
}
|
2019-06-08 14:31:11 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|