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