1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-12 03:51:10 +02:00
goreleaser/internal/pipe/git/git.go
Carlos A Becker f90df0f5ec
fix: getting previous tag
Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
2022-10-17 22:39:32 -03:00

312 lines
7.5 KiB
Go

package git
import (
"fmt"
"net/url"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/caarlos0/log"
"github.com/goreleaser/goreleaser/internal/git"
"github.com/goreleaser/goreleaser/internal/pipe"
"github.com/goreleaser/goreleaser/pkg/context"
)
// Pipe that sets up git state.
type Pipe struct{}
func (Pipe) String() string {
return "getting and validating git state"
}
// Run the pipe.
func (Pipe) Run(ctx *context.Context) error {
if _, err := exec.LookPath("git"); err != nil {
return ErrNoGit
}
info, err := getInfo(ctx)
if err != nil {
return err
}
ctx.Git = info
log.WithField("commit", info.Commit).WithField("latest tag", info.CurrentTag).Info("building...")
ctx.Version = strings.TrimPrefix(ctx.Git.CurrentTag, "v")
return validate(ctx)
}
// nolint: gochecknoglobals
var fakeInfo = context.GitInfo{
Branch: "none",
CurrentTag: "v0.0.0",
Commit: "none",
ShortCommit: "none",
FullCommit: "none",
Summary: "none",
}
func getInfo(ctx *context.Context) (context.GitInfo, error) {
if !git.IsRepo(ctx) && ctx.Snapshot {
log.Warn("accepting to run without a git repo because this is a snapshot")
return fakeInfo, nil
}
if !git.IsRepo(ctx) {
return context.GitInfo{}, ErrNotRepository
}
info, err := getGitInfo(ctx)
if err != nil && ctx.Snapshot {
log.WithError(err).Warn("ignoring errors because this is a snapshot")
if info.Commit == "" {
info = fakeInfo
}
return info, nil
}
return info, err
}
func getGitInfo(ctx *context.Context) (context.GitInfo, error) {
branch, err := getBranch(ctx)
if err != nil {
return context.GitInfo{}, fmt.Errorf("couldn't get current branch: %w", err)
}
short, err := getShortCommit(ctx)
if err != nil {
return context.GitInfo{}, fmt.Errorf("couldn't get current commit: %w", err)
}
full, err := getFullCommit(ctx)
if err != nil {
return context.GitInfo{}, fmt.Errorf("couldn't get current commit: %w", err)
}
date, err := getCommitDate(ctx)
if err != nil {
return context.GitInfo{}, fmt.Errorf("couldn't get commit date: %w", err)
}
summary, err := getSummary(ctx)
if err != nil {
return context.GitInfo{}, fmt.Errorf("couldn't get summary: %w", err)
}
gitURL, err := getURL(ctx)
if err != nil {
return context.GitInfo{}, fmt.Errorf("couldn't get remote URL: %w", err)
}
if strings.HasPrefix(gitURL, "https://") {
u, err := url.Parse(gitURL)
if err != nil {
return context.GitInfo{}, fmt.Errorf("couldn't parse remote URL: %w", err)
}
u.User = nil
gitURL = u.String()
}
tag, err := getTag(ctx)
if err != nil {
return context.GitInfo{
Branch: branch,
Commit: full,
FullCommit: full,
ShortCommit: short,
CommitDate: date,
URL: gitURL,
CurrentTag: "v0.0.0",
Summary: summary,
}, ErrNoTag
}
subject, err := getTagWithFormat(ctx, tag, "contents:subject")
if err != nil {
return context.GitInfo{}, fmt.Errorf("couldn't get tag subject: %w", err)
}
contents, err := getTagWithFormat(ctx, tag, "contents")
if err != nil {
return context.GitInfo{}, fmt.Errorf("couldn't get tag contents: %w", err)
}
body, err := getTagWithFormat(ctx, tag, "contents:body")
if err != nil {
return context.GitInfo{}, fmt.Errorf("couldn't get tag content body: %w", err)
}
previous, err := getPreviousTag(ctx, tag)
if err != nil {
// shouldn't error, will only affect templates
log.Warnf("couldn't find any tags before %q", tag)
}
return context.GitInfo{
Branch: branch,
CurrentTag: tag,
PreviousTag: previous,
Commit: full,
FullCommit: full,
ShortCommit: short,
CommitDate: date,
URL: gitURL,
Summary: summary,
TagSubject: subject,
TagContents: contents,
TagBody: body,
}, nil
}
func validate(ctx *context.Context) error {
if ctx.Snapshot {
return pipe.ErrSnapshotEnabled
}
if ctx.SkipValidate {
return pipe.ErrSkipValidateEnabled
}
if _, err := os.Stat(".git/shallow"); err == nil {
log.Warn("running against a shallow clone - check your CI documentation at https://goreleaser.com/ci")
}
if err := CheckDirty(ctx); err != nil {
return err
}
_, err := git.Clean(git.Run(ctx, "describe", "--exact-match", "--tags", "--match", ctx.Git.CurrentTag))
if err != nil {
return ErrWrongRef{
commit: ctx.Git.Commit,
tag: ctx.Git.CurrentTag,
}
}
return nil
}
// CheckDirty returns an error if the current git repository is dirty.
func CheckDirty(ctx *context.Context) error {
out, err := git.Run(ctx, "status", "--porcelain")
if strings.TrimSpace(out) != "" || err != nil {
return ErrDirty{status: out}
}
return nil
}
func getBranch(ctx *context.Context) (string, error) {
return git.Clean(git.Run(ctx, "rev-parse", "--abbrev-ref", "HEAD", "--quiet"))
}
func getCommitDate(ctx *context.Context) (time.Time, error) {
ct, err := git.Clean(git.Run(ctx, "show", "--format='%ct'", "HEAD", "--quiet"))
if err != nil {
return time.Time{}, err
}
if ct == "" {
return time.Time{}, nil
}
i, err := strconv.ParseInt(ct, 10, 64)
if err != nil {
return time.Time{}, err
}
t := time.Unix(i, 0).UTC()
return t, nil
}
func getShortCommit(ctx *context.Context) (string, error) {
return git.Clean(git.Run(ctx, "show", "--format=%h", "HEAD", "--quiet"))
}
func getFullCommit(ctx *context.Context) (string, error) {
return git.Clean(git.Run(ctx, "show", "--format=%H", "HEAD", "--quiet"))
}
func getSummary(ctx *context.Context) (string, error) {
return git.Clean(git.Run(ctx, "describe", "--always", "--dirty", "--tags"))
}
func getTagWithFormat(ctx *context.Context, tag, format string) (string, error) {
out, err := git.Run(ctx, "tag", "-l", "--format='%("+format+")'", tag)
return strings.TrimSpace(strings.TrimSuffix(strings.ReplaceAll(out, "'", ""), "\n\n")), err
}
func getTag(ctx *context.Context) (string, error) {
for _, fn := range []func() ([]string, error){
getFromEnv("GORELEASER_CURRENT_TAG"),
func() ([]string, error) {
return gitTagsPointingAt(ctx, "HEAD")
},
func() ([]string, error) {
// this will get the last tag, even if it wasn't made against the
// last commit...
return git.CleanAllLines(gitDescribe(ctx, "HEAD"))
},
} {
tags, err := fn()
if len(tags) > 0 {
return tags[0], err
}
if err != nil {
return "", err
}
}
return "", nil
}
func getPreviousTag(ctx *context.Context, current string) (string, error) {
for _, fn := range []func() ([]string, error){
getFromEnv("GORELEASER_PREVIOUS_TAG"),
func() ([]string, error) {
sha, err := previousTagSha(ctx, current)
if err != nil {
return nil, err
}
return gitTagsPointingAt(ctx, sha)
},
} {
tags, err := fn()
if len(tags) > 0 {
return tags[0], err
}
if err != nil {
return "", err
}
}
return "", nil
}
func gitTagsPointingAt(ctx *context.Context, ref string) ([]string, error) {
return git.CleanAllLines(git.Run(
ctx,
"tag",
"--points-at",
ref,
"--sort",
"-version:refname",
))
}
func gitDescribe(ctx *context.Context, ref string) (string, error) {
return git.Clean(git.Run(
ctx,
"describe",
"--tags",
"--abbrev=0",
ref,
))
}
func previousTagSha(ctx *context.Context, current string) (string, error) {
tag, err := gitDescribe(ctx, fmt.Sprintf("tags/%s^", current))
if err != nil {
return "", err
}
return git.Clean(git.Run(ctx, "rev-list", "-n1", tag))
}
func getURL(ctx *context.Context) (string, error) {
return git.Clean(git.Run(ctx, "ls-remote", "--get-url"))
}
func getFromEnv(s string) func() ([]string, error) {
return func() ([]string, error) {
if tag := os.Getenv(s); tag != "" {
return []string{tag}, nil
}
return nil, nil
}
}