You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

repo_tag.go 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package git
  6. import (
  7. "fmt"
  8. "strings"
  9. "github.com/mcuadros/go-version"
  10. "gopkg.in/src-d/go-git.v4/plumbing"
  11. )
  12. // TagPrefix tags prefix path on the repository
  13. const TagPrefix = "refs/tags/"
  14. // IsTagExist returns true if given tag exists in the repository.
  15. func IsTagExist(repoPath, name string) bool {
  16. return IsReferenceExist(repoPath, TagPrefix+name)
  17. }
  18. // IsTagExist returns true if given tag exists in the repository.
  19. func (repo *Repository) IsTagExist(name string) bool {
  20. _, err := repo.gogitRepo.Reference(plumbing.ReferenceName(TagPrefix+name), true)
  21. return err == nil
  22. }
  23. // CreateTag create one tag in the repository
  24. func (repo *Repository) CreateTag(name, revision string) error {
  25. _, err := NewCommand("tag", name, revision).RunInDir(repo.Path)
  26. return err
  27. }
  28. // CreateAnnotatedTag create one annotated tag in the repository
  29. func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error {
  30. _, err := NewCommand("tag", "-a", "-m", message, name, revision).RunInDir(repo.Path)
  31. return err
  32. }
  33. func (repo *Repository) getTag(id SHA1) (*Tag, error) {
  34. t, ok := repo.tagCache.Get(id.String())
  35. if ok {
  36. log("Hit cache: %s", id)
  37. tagClone := *t.(*Tag)
  38. return &tagClone, nil
  39. }
  40. // Get tag name
  41. name, err := repo.GetTagNameBySHA(id.String())
  42. if err != nil {
  43. return nil, err
  44. }
  45. tp, err := repo.GetTagType(id)
  46. if err != nil {
  47. return nil, err
  48. }
  49. // Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object
  50. commitIDStr, err := repo.GetTagCommitID(name)
  51. if err != nil {
  52. // every tag should have a commit ID so return all errors
  53. return nil, err
  54. }
  55. commitID, err := NewIDFromString(commitIDStr)
  56. if err != nil {
  57. return nil, err
  58. }
  59. // tagID defaults to the commit ID as the tag ID and then tries to get a tag ID (only annotated tags)
  60. tagID := commitID
  61. if tagIDStr, err := repo.GetTagID(name); err != nil {
  62. // if the err is NotExist then we can ignore and just keep tagID as ID (is lightweight tag)
  63. // all other errors we return
  64. if !IsErrNotExist(err) {
  65. return nil, err
  66. }
  67. } else {
  68. tagID, err = NewIDFromString(tagIDStr)
  69. if err != nil {
  70. return nil, err
  71. }
  72. }
  73. // If type is "commit, the tag is a lightweight tag
  74. if ObjectType(tp) == ObjectCommit {
  75. commit, err := repo.GetCommit(id.String())
  76. if err != nil {
  77. return nil, err
  78. }
  79. tag := &Tag{
  80. Name: name,
  81. ID: tagID,
  82. Object: commitID,
  83. Type: string(ObjectCommit),
  84. Tagger: commit.Committer,
  85. Message: commit.Message(),
  86. repo: repo,
  87. }
  88. repo.tagCache.Set(id.String(), tag)
  89. return tag, nil
  90. }
  91. // The tag is an annotated tag with a message.
  92. data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path)
  93. if err != nil {
  94. return nil, err
  95. }
  96. tag, err := parseTagData(data)
  97. if err != nil {
  98. return nil, err
  99. }
  100. tag.Name = name
  101. tag.ID = id
  102. tag.repo = repo
  103. tag.Type = tp
  104. repo.tagCache.Set(id.String(), tag)
  105. return tag, nil
  106. }
  107. // GetTagNameBySHA returns the name of a tag from its tag object SHA or commit SHA
  108. func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
  109. if len(sha) < 5 {
  110. return "", fmt.Errorf("SHA is too short: %s", sha)
  111. }
  112. stdout, err := NewCommand("show-ref", "--tags", "-d").RunInDir(repo.Path)
  113. if err != nil {
  114. return "", err
  115. }
  116. tagRefs := strings.Split(stdout, "\n")
  117. for _, tagRef := range tagRefs {
  118. if len(strings.TrimSpace(tagRef)) > 0 {
  119. fields := strings.Fields(tagRef)
  120. if strings.HasPrefix(fields[0], sha) && strings.HasPrefix(fields[1], TagPrefix) {
  121. name := fields[1][len(TagPrefix):]
  122. // annotated tags show up twice, their name for commit ID is suffixed with ^{}
  123. name = strings.TrimSuffix(name, "^{}")
  124. return name, nil
  125. }
  126. }
  127. }
  128. return "", ErrNotExist{ID: sha}
  129. }
  130. // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
  131. func (repo *Repository) GetTagID(name string) (string, error) {
  132. stdout, err := NewCommand("show-ref", name).RunInDir(repo.Path)
  133. if err != nil {
  134. return "", err
  135. }
  136. fields := strings.Fields(stdout)
  137. if len(fields) != 2 {
  138. return "", ErrNotExist{ID: name}
  139. }
  140. return fields[0], nil
  141. }
  142. // GetTag returns a Git tag by given name.
  143. func (repo *Repository) GetTag(name string) (*Tag, error) {
  144. idStr, err := repo.GetTagID(name)
  145. if err != nil {
  146. return nil, err
  147. }
  148. id, err := NewIDFromString(idStr)
  149. if err != nil {
  150. return nil, err
  151. }
  152. tag, err := repo.getTag(id)
  153. if err != nil {
  154. return nil, err
  155. }
  156. return tag, nil
  157. }
  158. // GetTagInfos returns all tag infos of the repository.
  159. func (repo *Repository) GetTagInfos() ([]*Tag, error) {
  160. // TODO this a slow implementation, makes one git command per tag
  161. stdout, err := NewCommand("tag").RunInDir(repo.Path)
  162. if err != nil {
  163. return nil, err
  164. }
  165. tagNames := strings.Split(strings.TrimRight(stdout, "\n"), "\n")
  166. var tags = make([]*Tag, 0, len(tagNames))
  167. for _, tagName := range tagNames {
  168. tagName = strings.TrimSpace(tagName)
  169. if len(tagName) == 0 {
  170. continue
  171. }
  172. tag, err := repo.GetTag(tagName)
  173. if err != nil {
  174. return nil, err
  175. }
  176. tag.Name = tagName
  177. tags = append(tags, tag)
  178. }
  179. sortTagsByTime(tags)
  180. return tags, nil
  181. }
  182. // GetTags returns all tags of the repository.
  183. func (repo *Repository) GetTags() ([]string, error) {
  184. var tagNames []string
  185. tags, err := repo.gogitRepo.Tags()
  186. if err != nil {
  187. return nil, err
  188. }
  189. _ = tags.ForEach(func(tag *plumbing.Reference) error {
  190. tagNames = append(tagNames, strings.TrimPrefix(tag.Name().String(), TagPrefix))
  191. return nil
  192. })
  193. version.Sort(tagNames)
  194. // Reverse order
  195. for i := 0; i < len(tagNames)/2; i++ {
  196. j := len(tagNames) - i - 1
  197. tagNames[i], tagNames[j] = tagNames[j], tagNames[i]
  198. }
  199. return tagNames, nil
  200. }
  201. // GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
  202. func (repo *Repository) GetTagType(id SHA1) (string, error) {
  203. // Get tag type
  204. stdout, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
  205. if err != nil {
  206. return "", err
  207. }
  208. if len(stdout) == 0 {
  209. return "", ErrNotExist{ID: id.String()}
  210. }
  211. return strings.TrimSpace(stdout), nil
  212. }
  213. // GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag
  214. func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) {
  215. id, err := NewIDFromString(sha)
  216. if err != nil {
  217. return nil, err
  218. }
  219. // Tag type must be "tag" (annotated) and not a "commit" (lightweight) tag
  220. if tagType, err := repo.GetTagType(id); err != nil {
  221. return nil, err
  222. } else if ObjectType(tagType) != ObjectTag {
  223. // not an annotated tag
  224. return nil, ErrNotExist{ID: id.String()}
  225. }
  226. tag, err := repo.getTag(id)
  227. if err != nil {
  228. return nil, err
  229. }
  230. return tag, nil
  231. }