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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package git
  5. import (
  6. "context"
  7. "fmt"
  8. "io"
  9. "strings"
  10. "code.gitea.io/gitea/modules/git/foreachref"
  11. "code.gitea.io/gitea/modules/util"
  12. )
  13. // TagPrefix tags prefix path on the repository
  14. const TagPrefix = "refs/tags/"
  15. // IsTagExist returns true if given tag exists in the repository.
  16. func IsTagExist(ctx context.Context, repoPath, name string) bool {
  17. return IsReferenceExist(ctx, repoPath, TagPrefix+name)
  18. }
  19. // CreateTag create one tag in the repository
  20. func (repo *Repository) CreateTag(name, revision string) error {
  21. _, _, err := NewCommand(repo.Ctx, "tag").AddDashesAndList(name, revision).RunStdString(&RunOpts{Dir: repo.Path})
  22. return err
  23. }
  24. // CreateAnnotatedTag create one annotated tag in the repository
  25. func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error {
  26. _, _, err := NewCommand(repo.Ctx, "tag", "-a", "-m").AddDynamicArguments(message).AddDashesAndList(name, revision).RunStdString(&RunOpts{Dir: repo.Path})
  27. return err
  28. }
  29. // GetTagNameBySHA returns the name of a tag from its tag object SHA or commit SHA
  30. func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
  31. if len(sha) < 5 {
  32. return "", fmt.Errorf("SHA is too short: %s", sha)
  33. }
  34. stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--tags", "-d").RunStdString(&RunOpts{Dir: repo.Path})
  35. if err != nil {
  36. return "", err
  37. }
  38. tagRefs := strings.Split(stdout, "\n")
  39. for _, tagRef := range tagRefs {
  40. if len(strings.TrimSpace(tagRef)) > 0 {
  41. fields := strings.Fields(tagRef)
  42. if strings.HasPrefix(fields[0], sha) && strings.HasPrefix(fields[1], TagPrefix) {
  43. name := fields[1][len(TagPrefix):]
  44. // annotated tags show up twice, we should only return if is not the ^{} ref
  45. if !strings.HasSuffix(name, "^{}") {
  46. return name, nil
  47. }
  48. }
  49. }
  50. }
  51. return "", ErrNotExist{ID: sha}
  52. }
  53. // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
  54. func (repo *Repository) GetTagID(name string) (string, error) {
  55. stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--tags").AddDashesAndList(name).RunStdString(&RunOpts{Dir: repo.Path})
  56. if err != nil {
  57. return "", err
  58. }
  59. // Make sure exact match is used: "v1" != "release/v1"
  60. for _, line := range strings.Split(stdout, "\n") {
  61. fields := strings.Fields(line)
  62. if len(fields) == 2 && fields[1] == "refs/tags/"+name {
  63. return fields[0], nil
  64. }
  65. }
  66. return "", ErrNotExist{ID: name}
  67. }
  68. // GetTag returns a Git tag by given name.
  69. func (repo *Repository) GetTag(name string) (*Tag, error) {
  70. idStr, err := repo.GetTagID(name)
  71. if err != nil {
  72. return nil, err
  73. }
  74. id, err := NewIDFromString(idStr)
  75. if err != nil {
  76. return nil, err
  77. }
  78. tag, err := repo.getTag(id, name)
  79. if err != nil {
  80. return nil, err
  81. }
  82. return tag, nil
  83. }
  84. // GetTagWithID returns a Git tag by given name and ID
  85. func (repo *Repository) GetTagWithID(idStr, name string) (*Tag, error) {
  86. id, err := NewIDFromString(idStr)
  87. if err != nil {
  88. return nil, err
  89. }
  90. tag, err := repo.getTag(id, name)
  91. if err != nil {
  92. return nil, err
  93. }
  94. return tag, nil
  95. }
  96. // GetTagInfos returns all tag infos of the repository.
  97. func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
  98. // Generally, refname:short should be equal to refname:lstrip=2 except core.warnAmbiguousRefs is used to select the strict abbreviation mode.
  99. // https://git-scm.com/docs/git-for-each-ref#Documentation/git-for-each-ref.txt-refname
  100. forEachRefFmt := foreachref.NewFormat("objecttype", "refname:lstrip=2", "object", "objectname", "creator", "contents", "contents:signature")
  101. stdoutReader, stdoutWriter := io.Pipe()
  102. defer stdoutReader.Close()
  103. defer stdoutWriter.Close()
  104. stderr := strings.Builder{}
  105. rc := &RunOpts{Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr}
  106. go func() {
  107. err := NewCommand(repo.Ctx, "for-each-ref").
  108. AddOptionFormat("--format=%s", forEachRefFmt.Flag()).
  109. AddArguments("--sort", "-*creatordate", "refs/tags").Run(rc)
  110. if err != nil {
  111. _ = stdoutWriter.CloseWithError(ConcatenateError(err, stderr.String()))
  112. } else {
  113. _ = stdoutWriter.Close()
  114. }
  115. }()
  116. var tags []*Tag
  117. parser := forEachRefFmt.Parser(stdoutReader)
  118. for {
  119. ref := parser.Next()
  120. if ref == nil {
  121. break
  122. }
  123. tag, err := parseTagRef(repo.objectFormat, ref)
  124. if err != nil {
  125. return nil, 0, fmt.Errorf("GetTagInfos: parse tag: %w", err)
  126. }
  127. tags = append(tags, tag)
  128. }
  129. if err := parser.Err(); err != nil {
  130. return nil, 0, fmt.Errorf("GetTagInfos: parse output: %w", err)
  131. }
  132. sortTagsByTime(tags)
  133. tagsTotal := len(tags)
  134. if page != 0 {
  135. tags = util.PaginateSlice(tags, page, pageSize).([]*Tag)
  136. }
  137. return tags, tagsTotal, nil
  138. }
  139. // parseTagRef parses a tag from a 'git for-each-ref'-produced reference.
  140. func parseTagRef(objectFormat ObjectFormat, ref map[string]string) (tag *Tag, err error) {
  141. tag = &Tag{
  142. Type: ref["objecttype"],
  143. Name: ref["refname:lstrip=2"],
  144. }
  145. tag.ID, err = NewIDFromString(ref["objectname"])
  146. if err != nil {
  147. return nil, fmt.Errorf("parse objectname '%s': %w", ref["objectname"], err)
  148. }
  149. if tag.Type == "commit" {
  150. // lightweight tag
  151. tag.Object = tag.ID
  152. } else {
  153. // annotated tag
  154. tag.Object, err = NewIDFromString(ref["object"])
  155. if err != nil {
  156. return nil, fmt.Errorf("parse object '%s': %w", ref["object"], err)
  157. }
  158. }
  159. tag.Tagger = parseSignatureFromCommitLine(ref["creator"])
  160. tag.Message = ref["contents"]
  161. // strip PGP signature if present in contents field
  162. pgpStart := strings.Index(tag.Message, beginpgp)
  163. if pgpStart >= 0 {
  164. tag.Message = tag.Message[0:pgpStart]
  165. }
  166. // annotated tag with GPG signature
  167. if tag.Type == "tag" && ref["contents:signature"] != "" {
  168. payload := fmt.Sprintf("object %s\ntype commit\ntag %s\ntagger %s\n\n%s\n",
  169. tag.Object, tag.Name, ref["creator"], strings.TrimSpace(tag.Message))
  170. tag.Signature = &CommitGPGSignature{
  171. Signature: ref["contents:signature"],
  172. Payload: payload,
  173. }
  174. }
  175. return tag, nil
  176. }
  177. // GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag
  178. func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) {
  179. id, err := NewIDFromString(sha)
  180. if err != nil {
  181. return nil, err
  182. }
  183. // Tag type must be "tag" (annotated) and not a "commit" (lightweight) tag
  184. if tagType, err := repo.GetTagType(id); err != nil {
  185. return nil, err
  186. } else if ObjectType(tagType) != ObjectTag {
  187. // not an annotated tag
  188. return nil, ErrNotExist{ID: id.String()}
  189. }
  190. // Get tag name
  191. name, err := repo.GetTagNameBySHA(id.String())
  192. if err != nil {
  193. return nil, err
  194. }
  195. tag, err := repo.getTag(id, name)
  196. if err != nil {
  197. return nil, err
  198. }
  199. return tag, nil
  200. }