Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

repo_tag.go 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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/go-git/go-git/v5/plumbing"
  10. "github.com/mcuadros/go-version"
  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, we should only return if is not the ^{} ref
  123. if !strings.HasSuffix(name, "^{}") {
  124. return name, nil
  125. }
  126. }
  127. }
  128. }
  129. return "", ErrNotExist{ID: sha}
  130. }
  131. // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
  132. func (repo *Repository) GetTagID(name string) (string, error) {
  133. stdout, err := NewCommand("show-ref", "--tags", "--", name).RunInDir(repo.Path)
  134. if err != nil {
  135. return "", err
  136. }
  137. // Make sure exact match is used: "v1" != "release/v1"
  138. for _, line := range strings.Split(stdout, "\n") {
  139. fields := strings.Fields(line)
  140. if len(fields) == 2 && fields[1] == "refs/tags/"+name {
  141. return fields[0], nil
  142. }
  143. }
  144. return "", ErrNotExist{ID: name}
  145. }
  146. // GetTag returns a Git tag by given name.
  147. func (repo *Repository) GetTag(name string) (*Tag, error) {
  148. idStr, err := repo.GetTagID(name)
  149. if err != nil {
  150. return nil, err
  151. }
  152. id, err := NewIDFromString(idStr)
  153. if err != nil {
  154. return nil, err
  155. }
  156. tag, err := repo.getTag(id)
  157. if err != nil {
  158. return nil, err
  159. }
  160. return tag, nil
  161. }
  162. // GetTagInfos returns all tag infos of the repository.
  163. func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, error) {
  164. // TODO this a slow implementation, makes one git command per tag
  165. stdout, err := NewCommand("tag").RunInDir(repo.Path)
  166. if err != nil {
  167. return nil, err
  168. }
  169. tagNames := strings.Split(strings.TrimRight(stdout, "\n"), "\n")
  170. if page != 0 {
  171. skip := (page - 1) * pageSize
  172. if skip >= len(tagNames) {
  173. return nil, nil
  174. }
  175. if (len(tagNames) - skip) < pageSize {
  176. pageSize = len(tagNames) - skip
  177. }
  178. tagNames = tagNames[skip : skip+pageSize]
  179. }
  180. var tags = make([]*Tag, 0, len(tagNames))
  181. for _, tagName := range tagNames {
  182. tagName = strings.TrimSpace(tagName)
  183. if len(tagName) == 0 {
  184. continue
  185. }
  186. tag, err := repo.GetTag(tagName)
  187. if err != nil {
  188. return nil, err
  189. }
  190. tag.Name = tagName
  191. tags = append(tags, tag)
  192. }
  193. sortTagsByTime(tags)
  194. return tags, nil
  195. }
  196. // GetTags returns all tags of the repository.
  197. func (repo *Repository) GetTags() ([]string, error) {
  198. var tagNames []string
  199. tags, err := repo.gogitRepo.Tags()
  200. if err != nil {
  201. return nil, err
  202. }
  203. _ = tags.ForEach(func(tag *plumbing.Reference) error {
  204. tagNames = append(tagNames, strings.TrimPrefix(tag.Name().String(), TagPrefix))
  205. return nil
  206. })
  207. version.Sort(tagNames)
  208. // Reverse order
  209. for i := 0; i < len(tagNames)/2; i++ {
  210. j := len(tagNames) - i - 1
  211. tagNames[i], tagNames[j] = tagNames[j], tagNames[i]
  212. }
  213. return tagNames, nil
  214. }
  215. // GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
  216. func (repo *Repository) GetTagType(id SHA1) (string, error) {
  217. // Get tag type
  218. stdout, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
  219. if err != nil {
  220. return "", err
  221. }
  222. if len(stdout) == 0 {
  223. return "", ErrNotExist{ID: id.String()}
  224. }
  225. return strings.TrimSpace(stdout), nil
  226. }
  227. // GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag
  228. func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) {
  229. id, err := NewIDFromString(sha)
  230. if err != nil {
  231. return nil, err
  232. }
  233. // Tag type must be "tag" (annotated) and not a "commit" (lightweight) tag
  234. if tagType, err := repo.GetTagType(id); err != nil {
  235. return nil, err
  236. } else if ObjectType(tagType) != ObjectTag {
  237. // not an annotated tag
  238. return nil, ErrNotExist{ID: id.String()}
  239. }
  240. tag, err := repo.getTag(id)
  241. if err != nil {
  242. return nil, err
  243. }
  244. return tag, nil
  245. }