* Fixes #2738 - /git/tags API * proper URLs * Adds function comments * Updates swagger * Removes newline from tag message * Removes trailing newline from commit message * Adds integration test * Removed debugging * Adds tests * Fixes bug where multiple tags of same commit show wrong tag name * Fix formatting * Removes unused varaible * Fix to annotated tag function names and response * Update modules/git/repo_tag.go Co-Authored-By: Lauris BH <lauris@nix.lv> * Uses TagPrefix * Changes per review, better error handling for getting tag and commit IDs * Fix to getting commit ID * Fix to getting commit ID * Fix to getting commit ID * Fix to getting commit ID
This commit is contained in:
parent
23a2ee3510
commit
8de0b0a3f0
16 changed files with 551 additions and 85 deletions
|
@ -31,15 +31,19 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
|
|||
if err = refsIter.ForEach(func(ref *plumbing.Reference) error {
|
||||
if ref.Name() != plumbing.HEAD && !ref.Name().IsRemote() &&
|
||||
(pattern == "" || strings.HasPrefix(ref.Name().String(), pattern)) {
|
||||
refType := string(ObjectCommit)
|
||||
if ref.Name().IsTag() {
|
||||
// tags can be of type `commit` (lightweight) or `tag` (annotated)
|
||||
if tagType, _ := repo.GetTagType(SHA1(ref.Hash())); err == nil {
|
||||
refType = tagType
|
||||
}
|
||||
}
|
||||
r := &Reference{
|
||||
Name: ref.Name().String(),
|
||||
Object: SHA1(ref.Hash()),
|
||||
Type: string(ObjectCommit),
|
||||
Type: refType,
|
||||
repo: repo,
|
||||
}
|
||||
if ref.Name().IsTag() {
|
||||
r.Type = string(ObjectTag)
|
||||
}
|
||||
refs = append(refs, r)
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -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 = strings.TrimSpace(tp)
|
||||
|
||||
// Tag is a commit.
|
||||
tp, err := repo.GetTagType(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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 tag 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(stdout, "\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
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ func TestRepository_GetTags(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, tags, 1)
|
||||
assert.EqualValues(t, "test", tags[0].Name)
|
||||
assert.EqualValues(t, "37991dec2c8e592043f47155ce4808d4580f9123", tags[0].ID.String())
|
||||
assert.EqualValues(t, "commit", tags[0].Type)
|
||||
assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[0].ID.String())
|
||||
assert.EqualValues(t, "tag", tags[0].Type)
|
||||
}
|
||||
|
||||
func TestRepository_GetTag(t *testing.T) {
|
||||
|
@ -35,10 +35,78 @@ func TestRepository_GetTag(t *testing.T) {
|
|||
bareRepo1, err := OpenRepository(clonedPath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tag, err := bareRepo1.GetTag("test")
|
||||
lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
|
||||
lTagName := "lightweightTag"
|
||||
bareRepo1.CreateTag(lTagName, lTagCommitID)
|
||||
|
||||
aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
|
||||
aTagName := "annotatedTag"
|
||||
aTagMessage := "my annotated message"
|
||||
bareRepo1.CreateAnnotatedTag(aTagName, aTagMessage, aTagCommitID)
|
||||
aTagID, _ := bareRepo1.GetTagID(aTagName)
|
||||
|
||||
lTag, err := bareRepo1.GetTag(lTagName)
|
||||
lTag.repo = nil
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, lTag)
|
||||
assert.EqualValues(t, lTagName, lTag.Name)
|
||||
assert.EqualValues(t, lTagCommitID, lTag.ID.String())
|
||||
assert.EqualValues(t, lTagCommitID, lTag.Object.String())
|
||||
assert.EqualValues(t, "commit", lTag.Type)
|
||||
|
||||
aTag, err := bareRepo1.GetTag(aTagName)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, aTag)
|
||||
assert.EqualValues(t, aTagName, aTag.Name)
|
||||
assert.EqualValues(t, aTagID, aTag.ID.String())
|
||||
assert.NotEqual(t, aTagID, aTag.Object.String())
|
||||
assert.EqualValues(t, aTagCommitID, aTag.Object.String())
|
||||
assert.EqualValues(t, "tag", aTag.Type)
|
||||
}
|
||||
|
||||
func TestRepository_GetAnnotatedTag(t *testing.T) {
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
|
||||
clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo1_TestRepository_GetTag")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(clonedPath)
|
||||
|
||||
bareRepo1, err := OpenRepository(clonedPath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
lTagCommitID := "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1"
|
||||
lTagName := "lightweightTag"
|
||||
bareRepo1.CreateTag(lTagName, lTagCommitID)
|
||||
|
||||
aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
|
||||
aTagName := "annotatedTag"
|
||||
aTagMessage := "my annotated message"
|
||||
bareRepo1.CreateAnnotatedTag(aTagName, aTagMessage, aTagCommitID)
|
||||
aTagID, _ := bareRepo1.GetTagID(aTagName)
|
||||
|
||||
// Try an annotated tag
|
||||
tag, err := bareRepo1.GetAnnotatedTag(aTagID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, tag)
|
||||
assert.EqualValues(t, "test", tag.Name)
|
||||
assert.EqualValues(t, "37991dec2c8e592043f47155ce4808d4580f9123", tag.ID.String())
|
||||
assert.EqualValues(t, "commit", tag.Type)
|
||||
assert.EqualValues(t, aTagName, tag.Name)
|
||||
assert.EqualValues(t, aTagID, tag.ID.String())
|
||||
assert.EqualValues(t, "tag", tag.Type)
|
||||
|
||||
// Annotated tag's Commit ID should fail
|
||||
tag2, err := bareRepo1.GetAnnotatedTag(aTagCommitID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrNotExist(err))
|
||||
assert.Nil(t, tag2)
|
||||
|
||||
// Annotated tag's name should fail
|
||||
tag3, err := bareRepo1.GetAnnotatedTag(aTagName)
|
||||
assert.Error(t, err)
|
||||
assert.Errorf(t, err, "Length must be 40: %d", len(aTagName))
|
||||
assert.Nil(t, tag3)
|
||||
|
||||
// Lightweight Tag should fail
|
||||
tag4, err := bareRepo1.GetAnnotatedTag(lTagCommitID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrNotExist(err))
|
||||
assert.Nil(t, tag4)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package git
|
|||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Tag represents a Git tag.
|
||||
|
@ -59,7 +60,7 @@ l:
|
|||
}
|
||||
nextline += eol + 1
|
||||
case eol == 0:
|
||||
tag.Message = string(data[nextline+1:])
|
||||
tag.Message = strings.TrimRight(string(data[nextline+1:]), "\n")
|
||||
break l
|
||||
default:
|
||||
break l
|
||||
|
|
|
@ -6,11 +6,27 @@ package structs
|
|||
|
||||
// Tag represents a repository tag
|
||||
type Tag struct {
|
||||
Name string `json:"name"`
|
||||
Commit struct {
|
||||
SHA string `json:"sha"`
|
||||
URL string `json:"url"`
|
||||
} `json:"commit"`
|
||||
ZipballURL string `json:"zipball_url"`
|
||||
TarballURL string `json:"tarball_url"`
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
Commit *CommitMeta `json:"commit"`
|
||||
ZipballURL string `json:"zipball_url"`
|
||||
TarballURL string `json:"tarball_url"`
|
||||
}
|
||||
|
||||
// AnnotatedTag represents an annotated tag
|
||||
type AnnotatedTag struct {
|
||||
Tag string `json:"tag"`
|
||||
SHA string `json:"sha"`
|
||||
URL string `json:"url"`
|
||||
Message string `json:"message"`
|
||||
Tagger *CommitUser `json:"tagger"`
|
||||
Object *AnnotatedTagObject `json:"object"`
|
||||
Verification *PayloadCommitVerification `json:"verification"`
|
||||
}
|
||||
|
||||
// AnnotatedTagObject contains meta information of the tag object
|
||||
type AnnotatedTagObject struct {
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url"`
|
||||
SHA string `json:"sha"`
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue