github.com/marianogappa/goreleaser@v0.26.2-0.20170715090149-96acd0a9fc46/pipeline/git/git.go (about) 1 // Package git implements the Pipe interface getting and validating the 2 // current git repository state 3 package git 4 5 import ( 6 "bytes" 7 "fmt" 8 "regexp" 9 "strings" 10 "text/template" 11 "time" 12 13 "github.com/apex/log" 14 "github.com/goreleaser/goreleaser/context" 15 ) 16 17 // ErrInvalidVersionFormat is return when the version isnt in a valid format 18 type ErrInvalidVersionFormat struct { 19 version string 20 } 21 22 func (e ErrInvalidVersionFormat) Error() string { 23 return fmt.Sprintf("%v is not in a valid version format", e.version) 24 } 25 26 // ErrDirty happens when the repo has uncommitted/unstashed changes 27 type ErrDirty struct { 28 status string 29 } 30 31 func (e ErrDirty) Error() string { 32 return fmt.Sprintf("git is currently in a dirty state:\n%v", e.status) 33 } 34 35 // ErrWrongRef happens when the HEAD reference is different from the tag being built 36 type ErrWrongRef struct { 37 commit, tag string 38 } 39 40 func (e ErrWrongRef) Error() string { 41 return fmt.Sprintf("git tag %v was not made against commit %v", e.tag, e.commit) 42 } 43 44 // ErrNoTag happens if the underlying git repository doesn't contain any tags 45 // but no snapshot-release was requested. 46 var ErrNoTag = fmt.Errorf("git doesn't contain any tags. Either add a tag or use --snapshot") 47 48 // Pipe for brew deployment 49 type Pipe struct{} 50 51 // Description of the pipe 52 func (Pipe) Description() string { 53 return "Getting and validating git state" 54 } 55 56 // Run the pipe 57 func (Pipe) Run(ctx *context.Context) (err error) { 58 tag, commit, err := getInfo() 59 if err != nil { 60 return 61 } 62 if tag == "" && !ctx.Snapshot { 63 return ErrNoTag 64 } 65 ctx.Git = context.GitInfo{ 66 CurrentTag: tag, 67 Commit: commit, 68 } 69 if err = setLog(ctx, tag, commit); err != nil { 70 return 71 } 72 if err = setVersion(ctx, tag, commit); err != nil { 73 return 74 } 75 if !ctx.Validate { 76 log.Warn("skipped validations because --skip-validate is set") 77 return nil 78 } 79 return validate(ctx, commit, tag) 80 } 81 82 func setVersion(ctx *context.Context, tag, commit string) (err error) { 83 if ctx.Snapshot { 84 snapshotName, err := getSnapshotName(ctx, tag, commit) 85 if err != nil { 86 return fmt.Errorf("failed to generate snapshot name: %s", err.Error()) 87 } 88 ctx.Version = snapshotName 89 return nil 90 } 91 // removes usual `v` prefix 92 ctx.Version = strings.TrimPrefix(tag, "v") 93 return 94 } 95 96 func setLog(ctx *context.Context, tag, commit string) (err error) { 97 if ctx.ReleaseNotes != "" { 98 return 99 } 100 var log string 101 if tag == "" { 102 log, err = getChangelog(commit) 103 } else { 104 log, err = getChangelog(tag) 105 } 106 if err != nil { 107 return err 108 } 109 ctx.ReleaseNotes = fmt.Sprintf("## Changelog\n\n%v", log) 110 return nil 111 } 112 113 type snapshotNameData struct { 114 Commit string 115 Tag string 116 Timestamp int64 117 } 118 119 func getSnapshotName(ctx *context.Context, tag, commit string) (string, error) { 120 tmpl, err := template.New("snapshot").Parse(ctx.Config.Snapshot.NameTemplate) 121 var out bytes.Buffer 122 if err != nil { 123 return "", err 124 } 125 var data = snapshotNameData{ 126 Commit: commit, 127 Tag: tag, 128 Timestamp: time.Now().Unix(), 129 } 130 err = tmpl.Execute(&out, data) 131 return out.String(), err 132 } 133 134 func validate(ctx *context.Context, commit, tag string) error { 135 out, err := git("status", "--porcelain") 136 if strings.TrimSpace(out) != "" || err != nil { 137 return ErrDirty{out} 138 } 139 if ctx.Snapshot { 140 return nil 141 } 142 if !regexp.MustCompile("^[0-9.]+").MatchString(ctx.Version) { 143 return ErrInvalidVersionFormat{ctx.Version} 144 } 145 _, err = cleanGit("describe", "--exact-match", "--tags", "--match", tag) 146 if err != nil { 147 return ErrWrongRef{commit, tag} 148 } 149 return nil 150 } 151 152 func getChangelog(tag string) (string, error) { 153 prev, err := previous(tag) 154 if err != nil { 155 return "", err 156 } 157 if !prev.Tag { 158 return gitLog(prev.SHA, tag) 159 } 160 return gitLog(fmt.Sprintf("%v..%v", prev.SHA, tag)) 161 } 162 163 func gitLog(refs ...string) (string, error) { 164 var args = []string{"log", "--pretty=oneline", "--abbrev-commit"} 165 args = append(args, refs...) 166 return git(args...) 167 } 168 169 func getInfo() (tag, commit string, err error) { 170 tag, err = cleanGit("describe", "--tags", "--abbrev=0") 171 if err != nil { 172 log.WithError(err).Info("failed to retrieve current tag") 173 } 174 commit, err = cleanGit("show", "--format='%H'", "HEAD") 175 return 176 } 177 178 func previous(tag string) (result ref, err error) { 179 result.Tag = true 180 result.SHA, err = cleanGit("describe", "--tags", "--abbrev=0", tag+"^") 181 if err != nil { 182 result.Tag = false 183 result.SHA, err = cleanGit("rev-list", "--max-parents=0", "HEAD") 184 } 185 return 186 } 187 188 type ref struct { 189 Tag bool 190 SHA string 191 }