github.com/triarius/goreleaser@v1.12.5/internal/pipe/git/git.go (about) 1 package git 2 3 import ( 4 "fmt" 5 "net/url" 6 "os" 7 "os/exec" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/caarlos0/log" 13 "github.com/triarius/goreleaser/internal/git" 14 "github.com/triarius/goreleaser/internal/pipe" 15 "github.com/triarius/goreleaser/pkg/context" 16 ) 17 18 // Pipe that sets up git state. 19 type Pipe struct{} 20 21 func (Pipe) String() string { 22 return "getting and validating git state" 23 } 24 25 // Run the pipe. 26 func (Pipe) Run(ctx *context.Context) error { 27 if _, err := exec.LookPath("git"); err != nil { 28 return ErrNoGit 29 } 30 info, err := getInfo(ctx) 31 if err != nil { 32 return err 33 } 34 ctx.Git = info 35 log.WithField("commit", info.Commit).WithField("latest tag", info.CurrentTag).Info("building...") 36 ctx.Version = strings.TrimPrefix(ctx.Git.CurrentTag, "v") 37 return validate(ctx) 38 } 39 40 // nolint: gochecknoglobals 41 var fakeInfo = context.GitInfo{ 42 Branch: "none", 43 CurrentTag: "v0.0.0", 44 Commit: "none", 45 ShortCommit: "none", 46 FullCommit: "none", 47 Summary: "none", 48 } 49 50 func getInfo(ctx *context.Context) (context.GitInfo, error) { 51 if !git.IsRepo(ctx) && ctx.Snapshot { 52 log.Warn("accepting to run without a git repo because this is a snapshot") 53 return fakeInfo, nil 54 } 55 if !git.IsRepo(ctx) { 56 return context.GitInfo{}, ErrNotRepository 57 } 58 info, err := getGitInfo(ctx) 59 if err != nil && ctx.Snapshot { 60 log.WithError(err).Warn("ignoring errors because this is a snapshot") 61 if info.Commit == "" { 62 info = fakeInfo 63 } 64 return info, nil 65 } 66 return info, err 67 } 68 69 func getGitInfo(ctx *context.Context) (context.GitInfo, error) { 70 branch, err := getBranch(ctx) 71 if err != nil { 72 return context.GitInfo{}, fmt.Errorf("couldn't get current branch: %w", err) 73 } 74 short, err := getShortCommit(ctx) 75 if err != nil { 76 return context.GitInfo{}, fmt.Errorf("couldn't get current commit: %w", err) 77 } 78 full, err := getFullCommit(ctx) 79 if err != nil { 80 return context.GitInfo{}, fmt.Errorf("couldn't get current commit: %w", err) 81 } 82 date, err := getCommitDate(ctx) 83 if err != nil { 84 return context.GitInfo{}, fmt.Errorf("couldn't get commit date: %w", err) 85 } 86 summary, err := getSummary(ctx) 87 if err != nil { 88 return context.GitInfo{}, fmt.Errorf("couldn't get summary: %w", err) 89 } 90 gitURL, err := getURL(ctx) 91 if err != nil { 92 return context.GitInfo{}, fmt.Errorf("couldn't get remote URL: %w", err) 93 } 94 95 if strings.HasPrefix(gitURL, "https://") { 96 u, err := url.Parse(gitURL) 97 if err != nil { 98 return context.GitInfo{}, fmt.Errorf("couldn't parse remote URL: %w", err) 99 } 100 u.User = nil 101 gitURL = u.String() 102 } 103 104 tag, err := getTag(ctx) 105 if err != nil { 106 return context.GitInfo{ 107 Branch: branch, 108 Commit: full, 109 FullCommit: full, 110 ShortCommit: short, 111 CommitDate: date, 112 URL: gitURL, 113 CurrentTag: "v0.0.0", 114 Summary: summary, 115 }, ErrNoTag 116 } 117 118 subject, err := getTagWithFormat(ctx, tag, "contents:subject") 119 if err != nil { 120 return context.GitInfo{}, fmt.Errorf("couldn't get tag subject: %w", err) 121 } 122 123 contents, err := getTagWithFormat(ctx, tag, "contents") 124 if err != nil { 125 return context.GitInfo{}, fmt.Errorf("couldn't get tag contents: %w", err) 126 } 127 128 body, err := getTagWithFormat(ctx, tag, "contents:body") 129 if err != nil { 130 return context.GitInfo{}, fmt.Errorf("couldn't get tag content body: %w", err) 131 } 132 133 previous, err := getPreviousTag(ctx, tag) 134 if err != nil { 135 // shouldn't error, will only affect templates 136 log.Warnf("couldn't find any tags before %q", tag) 137 } 138 139 return context.GitInfo{ 140 Branch: branch, 141 CurrentTag: tag, 142 PreviousTag: previous, 143 Commit: full, 144 FullCommit: full, 145 ShortCommit: short, 146 CommitDate: date, 147 URL: gitURL, 148 Summary: summary, 149 TagSubject: subject, 150 TagContents: contents, 151 TagBody: body, 152 }, nil 153 } 154 155 func validate(ctx *context.Context) error { 156 if ctx.Snapshot { 157 return pipe.ErrSnapshotEnabled 158 } 159 if ctx.SkipValidate { 160 return pipe.ErrSkipValidateEnabled 161 } 162 if _, err := os.Stat(".git/shallow"); err == nil { 163 log.Warn("running against a shallow clone - check your CI documentation at https://goreleaser.com/ci") 164 } 165 if err := CheckDirty(ctx); err != nil { 166 return err 167 } 168 _, err := git.Clean(git.Run(ctx, "describe", "--exact-match", "--tags", "--match", ctx.Git.CurrentTag)) 169 if err != nil { 170 return ErrWrongRef{ 171 commit: ctx.Git.Commit, 172 tag: ctx.Git.CurrentTag, 173 } 174 } 175 return nil 176 } 177 178 // CheckDirty returns an error if the current git repository is dirty. 179 func CheckDirty(ctx *context.Context) error { 180 out, err := git.Run(ctx, "status", "--porcelain") 181 if strings.TrimSpace(out) != "" || err != nil { 182 return ErrDirty{status: out} 183 } 184 return nil 185 } 186 187 func getBranch(ctx *context.Context) (string, error) { 188 return git.Clean(git.Run(ctx, "rev-parse", "--abbrev-ref", "HEAD", "--quiet")) 189 } 190 191 func getCommitDate(ctx *context.Context) (time.Time, error) { 192 ct, err := git.Clean(git.Run(ctx, "show", "--format='%ct'", "HEAD", "--quiet")) 193 if err != nil { 194 return time.Time{}, err 195 } 196 if ct == "" { 197 return time.Time{}, nil 198 } 199 i, err := strconv.ParseInt(ct, 10, 64) 200 if err != nil { 201 return time.Time{}, err 202 } 203 t := time.Unix(i, 0).UTC() 204 return t, nil 205 } 206 207 func getShortCommit(ctx *context.Context) (string, error) { 208 return git.Clean(git.Run(ctx, "show", "--format=%h", "HEAD", "--quiet")) 209 } 210 211 func getFullCommit(ctx *context.Context) (string, error) { 212 return git.Clean(git.Run(ctx, "show", "--format=%H", "HEAD", "--quiet")) 213 } 214 215 func getSummary(ctx *context.Context) (string, error) { 216 return git.Clean(git.Run(ctx, "describe", "--always", "--dirty", "--tags")) 217 } 218 219 func getTagWithFormat(ctx *context.Context, tag, format string) (string, error) { 220 out, err := git.Run(ctx, "tag", "-l", "--format='%("+format+")'", tag) 221 return strings.TrimSpace(strings.TrimSuffix(strings.ReplaceAll(out, "'", ""), "\n\n")), err 222 } 223 224 func getTag(ctx *context.Context) (string, error) { 225 for _, fn := range []func() ([]string, error){ 226 getFromEnv("GORELEASER_CURRENT_TAG"), 227 func() ([]string, error) { 228 return gitTagsPointingAt(ctx, "HEAD") 229 }, 230 func() ([]string, error) { 231 // this will get the last tag, even if it wasn't made against the 232 // last commit... 233 return git.CleanAllLines(gitDescribe(ctx, "HEAD")) 234 }, 235 } { 236 tags, err := fn() 237 if len(tags) > 0 { 238 return tags[0], err 239 } 240 if err != nil { 241 return "", err 242 } 243 } 244 245 return "", nil 246 } 247 248 func getPreviousTag(ctx *context.Context, current string) (string, error) { 249 for _, fn := range []func() ([]string, error){ 250 getFromEnv("GORELEASER_PREVIOUS_TAG"), 251 func() ([]string, error) { 252 sha, err := previousTagSha(ctx, current) 253 if err != nil { 254 return nil, err 255 } 256 return gitTagsPointingAt(ctx, sha) 257 }, 258 } { 259 tags, err := fn() 260 if len(tags) > 0 { 261 return tags[0], err 262 } 263 if err != nil { 264 return "", err 265 } 266 } 267 268 return "", nil 269 } 270 271 func gitTagsPointingAt(ctx *context.Context, ref string) ([]string, error) { 272 return git.CleanAllLines(git.Run( 273 ctx, 274 "tag", 275 "--points-at", 276 ref, 277 "--sort", 278 "-version:refname", 279 )) 280 } 281 282 func gitDescribe(ctx *context.Context, ref string) (string, error) { 283 return git.Clean(git.Run( 284 ctx, 285 "describe", 286 "--tags", 287 "--abbrev=0", 288 ref, 289 )) 290 } 291 292 func previousTagSha(ctx *context.Context, current string) (string, error) { 293 tag, err := gitDescribe(ctx, fmt.Sprintf("tags/%s^", current)) 294 if err != nil { 295 return "", err 296 } 297 return git.Clean(git.Run(ctx, "rev-list", "-n1", tag)) 298 } 299 300 func getURL(ctx *context.Context) (string, error) { 301 return git.Clean(git.Run(ctx, "ls-remote", "--get-url")) 302 } 303 304 func getFromEnv(s string) func() ([]string, error) { 305 return func() ([]string, error) { 306 if tag := os.Getenv(s); tag != "" { 307 return []string{tag}, nil 308 } 309 return nil, nil 310 } 311 }