-
Notifications
You must be signed in to change notification settings - Fork 7
/
main.go
186 lines (147 loc) · 5.65 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package main
import (
"bytes"
"flag"
"fmt"
"log"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/aoepeople/semanticore/internal"
"github.com/aoepeople/semanticore/internal/hook"
)
func try(err error) {
if err != nil {
panic(err)
}
}
var useBackend = flag.String("backend", os.Getenv("SEMANTICORE_BACKEND"), "configure backend use either \"github\" or \"gitlab\" - we'll try to autodetect if empty")
var createMajor = flag.Bool("major", false, "release major versions")
var createRelease = flag.Bool("release", true, "create release alongside tags")
var createMergeRequest = flag.Bool("merge-request", true, "create merge release for branch")
var authorName = flag.String("git-author-name", emptyFallback(os.Getenv("GIT_AUTHOR_NAME"), "Semanticore Bot"), "author name for the git commits, falls back to env var GIT_AUTHOR_NAME and afterwards to \"Semanticore Bot\"")
var authorEmail = flag.String("git-author-email", emptyFallback(os.Getenv("GIT_AUTHOR_EMAIL"), "[email protected]"), "author email for the git commits, falls back to env var GIT_AUTHOR_EMAIL and afterwards to \"[email protected]\"")
var committerName = flag.String("git-committer-name", emptyFallback(os.Getenv("GIT_COMMITTER_NAME"), "Semanticore Bot"), "committer name for the git commits, falls back to env var GIT_COMMITTER_NAME and afterwards to \"Semanticore Bot\"")
var committerEmail = flag.String("git-committer-email", emptyFallback(os.Getenv("GIT_COMMITTER_EMAIL"), "[email protected]"), "committer email for the git commits, falls back to env var GIT_COMMITTER_EMAIL and afterwards to \"[email protected]\"")
func main() {
flag.Parse()
dir := "."
if flag.NArg() > 0 {
dir = flag.Arg(0)
}
try(os.Chdir(dir))
repo, err := git.PlainOpen(".")
try(err)
remote, err := repo.Remote("origin")
try(err)
remoteUrl, err := url.Parse(remote.Config().URLs[0])
try(err)
repoId := strings.TrimSuffix(strings.TrimPrefix(remoteUrl.Path, "/"), ".git")
log.Printf("[semanticore] repository: %s at %s", repoId, remoteUrl.Host)
var backend internal.Backend
if os.Getenv("SEMANTICORE_TOKEN") == "" {
log.Println("[semanticore] SEMANTICORE_TOKEN unset, no merge requests will be handled")
} else if *useBackend == "github" || remoteUrl.Host == "github.com" {
backend = internal.NewGithubBackend(os.Getenv("SEMANTICORE_TOKEN"), repoId)
} else if *useBackend == "gitlab" || strings.Contains(remoteUrl.Host, "gitlab") {
backend = internal.NewGitlabBackend(os.Getenv("SEMANTICORE_TOKEN"), remoteUrl.Host, repoId)
}
head, err := repo.Head()
try(err)
repository, err := internal.ReadRepository(repo, *createMajor)
try(err)
if backend != nil && *createRelease {
repository.Release(backend)
}
changelog := repository.Changelog()
if changelog == "" {
log.Println("no changes detected, exiting...")
return
}
fmt.Println(changelog)
if !*createMergeRequest {
return
}
wt, err := repo.Worktree()
try(err)
filename := "Changelog.md"
files, err := wt.Filesystem.ReadDir(".")
try(err)
// detect case-sensitive filenames
for _, f := range files {
if !f.IsDir() && strings.ToLower(f.Name()) == "changelog.md" {
filename = f.Name()
}
}
cl, _ := os.ReadFile(filepath.Join(filename))
if strings.Contains(string(cl), "# Changelog\n\n") {
cl = bytes.Replace(cl, []byte("# Changelog\n\n"), []byte(changelog), 1)
} else if strings.Contains(string(cl), "# Changelog\n") {
cl = bytes.Replace(cl, []byte("# Changelog\n"), []byte(changelog), 1)
} else {
cl = append([]byte(changelog), cl...)
}
try(os.WriteFile(filepath.Join(filename), cl, 0644))
_, err = wt.Add(filename)
try(err)
hook.NpmUpdateVersionHook(wt, repository)
commit, err := wt.Commit(fmt.Sprintf("Release %s%d.%d.%d", repository.VPrefix, repository.Major, repository.Minor, repository.Patch), &git.CommitOptions{
Author: &object.Signature{
Name: *authorName,
Email: *authorEmail,
When: time.Now(),
},
Committer: &object.Signature{
Name: *committerName,
Email: *committerEmail,
When: time.Now(),
},
})
try(err)
log.Printf("[semanticore] committed changelog: %s", commit.String())
try(wt.Reset(&git.ResetOptions{
Commit: head.Hash(),
Mode: git.HardReset,
}))
if backend == nil {
log.Printf("no backend configured, keeping changes in a local commit: %s", commit.String())
return
}
try(repo.Push(&git.PushOptions{
RemoteName: "origin",
RefSpecs: []config.RefSpec{config.RefSpec(commit.String() + ":refs/heads/semanticore/release")},
Force: true,
Auth: backend,
Progress: os.Stdout,
}))
releasetype := "patch 🩹"
if repository.Breaking && *createMajor {
releasetype = "major 👏"
} else if len(repository.Features) > 0 {
releasetype = "minor 📦"
}
labels := "Release 🏆," + releasetype
description := fmt.Sprintf(`# Release %s%d.%d.%d 🏆
## Summary
There are %s commits since %s.
This is a %s release.
Merge this pull request to commit the changelog and have Semanticore create a new release on the next pipeline run.
%s
---
This changelog was generated by your friendly [Semanticore Release Bot](https://github.com/aoepeople/semanticore)
`, repository.VPrefix, repository.Major, repository.Minor, repository.Patch, strings.Join(repository.Details, ", "), repository.Latest, releasetype, strings.TrimSpace(changelog))
mainBranch, err := backend.MainBranch()
try(err)
try(backend.MergeRequest(string(mainBranch), fmt.Sprintf("Release %s%d.%d.%d", repository.VPrefix, repository.Major, repository.Minor, repository.Patch), description, labels))
}
func emptyFallback(s, fallback string) string {
if s == "" {
return fallback
}
return s
}