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.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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. "code.gitea.io/gitea/modules/log"
  10. "code.gitea.io/gitea/modules/util"
  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. // CreateTag create one tag in the repository
  19. func (repo *Repository) CreateTag(name, revision string) error {
  20. _, err := NewCommand("tag", "--", name, revision).RunInDir(repo.Path)
  21. return err
  22. }
  23. // CreateAnnotatedTag create one annotated tag in the repository
  24. func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error {
  25. _, err := NewCommand("tag", "-a", "-m", message, "--", name, revision).RunInDir(repo.Path)
  26. return err
  27. }
  28. func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) {
  29. t, ok := repo.tagCache.Get(tagID.String())
  30. if ok {
  31. log.Debug("Hit cache: %s", tagID)
  32. tagClone := *t.(*Tag)
  33. tagClone.Name = name // This is necessary because lightweight tags may have same id
  34. return &tagClone, nil
  35. }
  36. tp, err := repo.GetTagType(tagID)
  37. if err != nil {
  38. return nil, err
  39. }
  40. // Get the commit ID and tag ID (may be different for annotated tag) for the returned tag object
  41. commitIDStr, err := repo.GetTagCommitID(name)
  42. if err != nil {
  43. // every tag should have a commit ID so return all errors
  44. return nil, err
  45. }
  46. commitID, err := NewIDFromString(commitIDStr)
  47. if err != nil {
  48. return nil, err
  49. }
  50. // If type is "commit, the tag is a lightweight tag
  51. if ObjectType(tp) == ObjectCommit {
  52. commit, err := repo.GetCommit(commitIDStr)
  53. if err != nil {
  54. return nil, err
  55. }
  56. tag := &Tag{
  57. Name: name,
  58. ID: tagID,
  59. Object: commitID,
  60. Type: tp,
  61. Tagger: commit.Committer,
  62. Message: commit.Message(),
  63. repo: repo,
  64. }
  65. repo.tagCache.Set(tagID.String(), tag)
  66. return tag, nil
  67. }
  68. // The tag is an annotated tag with a message.
  69. data, err := NewCommand("cat-file", "-p", tagID.String()).RunInDirBytes(repo.Path)
  70. if err != nil {
  71. return nil, err
  72. }
  73. tag, err := parseTagData(data)
  74. if err != nil {
  75. return nil, err
  76. }
  77. tag.Name = name
  78. tag.ID = tagID
  79. tag.repo = repo
  80. tag.Type = tp
  81. repo.tagCache.Set(tagID.String(), tag)
  82. return tag, nil
  83. }
  84. // GetTagNameBySHA returns the name of a tag from its tag object SHA or commit SHA
  85. func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
  86. if len(sha) < 5 {
  87. return "", fmt.Errorf("SHA is too short: %s", sha)
  88. }
  89. stdout, err := NewCommand("show-ref", "--tags", "-d").RunInDir(repo.Path)
  90. if err != nil {
  91. return "", err
  92. }
  93. tagRefs := strings.Split(stdout, "\n")
  94. for _, tagRef := range tagRefs {
  95. if len(strings.TrimSpace(tagRef)) > 0 {
  96. fields := strings.Fields(tagRef)
  97. if strings.HasPrefix(fields[0], sha) && strings.HasPrefix(fields[1], TagPrefix) {
  98. name := fields[1][len(TagPrefix):]
  99. // annotated tags show up twice, we should only return if is not the ^{} ref
  100. if !strings.HasSuffix(name, "^{}") {
  101. return name, nil
  102. }
  103. }
  104. }
  105. }
  106. return "", ErrNotExist{ID: sha}
  107. }
  108. // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
  109. func (repo *Repository) GetTagID(name string) (string, error) {
  110. stdout, err := NewCommand("show-ref", "--tags", "--", name).RunInDir(repo.Path)
  111. if err != nil {
  112. return "", err
  113. }
  114. // Make sure exact match is used: "v1" != "release/v1"
  115. for _, line := range strings.Split(stdout, "\n") {
  116. fields := strings.Fields(line)
  117. if len(fields) == 2 && fields[1] == "refs/tags/"+name {
  118. return fields[0], nil
  119. }
  120. }
  121. return "", ErrNotExist{ID: name}
  122. }
  123. // GetTag returns a Git tag by given name.
  124. func (repo *Repository) GetTag(name string) (*Tag, error) {
  125. idStr, err := repo.GetTagID(name)
  126. if err != nil {
  127. return nil, err
  128. }
  129. id, err := NewIDFromString(idStr)
  130. if err != nil {
  131. return nil, err
  132. }
  133. tag, err := repo.getTag(id, name)
  134. if err != nil {
  135. return nil, err
  136. }
  137. return tag, nil
  138. }
  139. // GetTagInfos returns all tag infos of the repository.
  140. func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
  141. // TODO this a slow implementation, makes one git command per tag
  142. stdout, err := NewCommand("tag").RunInDir(repo.Path)
  143. if err != nil {
  144. return nil, 0, err
  145. }
  146. tagNames := strings.Split(strings.TrimRight(stdout, "\n"), "\n")
  147. tagsTotal := len(tagNames)
  148. if page != 0 {
  149. tagNames = util.PaginateSlice(tagNames, page, pageSize).([]string)
  150. }
  151. var tags = make([]*Tag, 0, len(tagNames))
  152. for _, tagName := range tagNames {
  153. tagName = strings.TrimSpace(tagName)
  154. if len(tagName) == 0 {
  155. continue
  156. }
  157. tag, err := repo.GetTag(tagName)
  158. if err != nil {
  159. return nil, tagsTotal, err
  160. }
  161. tag.Name = tagName
  162. tags = append(tags, tag)
  163. }
  164. sortTagsByTime(tags)
  165. return tags, tagsTotal, nil
  166. }
  167. // GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
  168. func (repo *Repository) GetTagType(id SHA1) (string, error) {
  169. // Get tag type
  170. stdout, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
  171. if err != nil {
  172. return "", err
  173. }
  174. if len(stdout) == 0 {
  175. return "", ErrNotExist{ID: id.String()}
  176. }
  177. return strings.TrimSpace(stdout), nil
  178. }
  179. // GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag
  180. func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) {
  181. id, err := NewIDFromString(sha)
  182. if err != nil {
  183. return nil, err
  184. }
  185. // Tag type must be "tag" (annotated) and not a "commit" (lightweight) tag
  186. if tagType, err := repo.GetTagType(id); err != nil {
  187. return nil, err
  188. } else if ObjectType(tagType) != ObjectTag {
  189. // not an annotated tag
  190. return nil, ErrNotExist{ID: id.String()}
  191. }
  192. // Get tag name
  193. name, err := repo.GetTagNameBySHA(id.String())
  194. if err != nil {
  195. return nil, err
  196. }
  197. tag, err := repo.getTag(id, name)
  198. if err != nil {
  199. return nil, err
  200. }
  201. return tag, nil
  202. }