Skip to content

Commit 11d8b2a

Browse files
author
彭博
committed
支持输出版本发布到标准输出或文件
1 parent 8b0db0c commit 11d8b2a

File tree

7 files changed

+429
-53
lines changed

7 files changed

+429
-53
lines changed

cmd/release.go

Lines changed: 238 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ package cmd
1616

1717
import (
1818
"fmt"
19+
"io"
1920
"net/url"
2021
"os"
22+
"reflect"
2123
"regexp"
2224
"strconv"
2325
"strings"
26+
"text/template"
27+
"time"
2428

2529
"e.coding.net/codingcorp/coding-cli/pkg/model"
2630

@@ -32,6 +36,9 @@ const (
3236
defaultBranchURI = "/api/user/codingcorp/project/coding-dev/git/branches/default"
3337
commitDetailURI = "/api/user/codingcorp/project/coding-dev/git/commit/%s"
3438
diffURI = "/api/user/codingcorp/project/coding-dev/git/compare_v2?source=%s&target=%s&w=&prefix="
39+
mergeURI = "/api/user/codingcorp/project/coding-dev/git/merge/%d"
40+
gitBlobURI = "/api/user/codingcorp/project/coding-dev/git/blob/%s"
41+
currentUserURI = "/api/current_user"
3542
diffTemplate = "/p/coding-dev/git/compare/%s...%s"
3643
)
3744

@@ -92,14 +99,22 @@ var services = [][]string{
9299
{"coding", "scheduler"},
93100
}
94101

102+
var productOnlyServices = map[string][]string{
103+
"enterprise-saas": []string{"enterprise-front", "e-admin", "e-build-artifact", "e-coding", "e-scheduler"},
104+
"professional": []string{"platform-front", "admin", "coding", "scheduler"},
105+
}
106+
107+
var output string
108+
var rtype string
109+
var product string
110+
95111
// releaseCmd represents the release command
96112
var releaseCmd = &cobra.Command{
97113
Use: "release [分支、提交或标签]",
98114
Short: "创建版本发布",
99115
Long: `创建版本发布
100116
101-
为分支、提交或标签(简称 ref)创建版本发布,版本发布分为常规发布和紧急修复两类。
102-
创建版本发布时,默认发布类型为常规更新,可通过 -t(--type) 指定类型。创建必须提供 ref 信息即可。`,
117+
为分支、提交或标签(简称 ref)创建版本发布,版本发布分为常规发布和紧急修复两类。`,
103118
Args: cobra.MinimumNArgs(1),
104119
Run: func(cmd *cobra.Command, args []string) {
105120
if len(args) == 0 {
@@ -112,67 +127,256 @@ var releaseCmd = &cobra.Command{
112127
fmt.Fprintf(os.Stderr, "无法获取默认分支, %v\n", err)
113128
return
114129
}
115-
err = release(source, target)
130+
r, err := release(source, target)
116131
if err != nil {
117132
fmt.Fprintf(os.Stderr, "无法创建版本发布, %v\n", err)
118133
return
119134
}
135+
// 计算发布版本号
136+
patch := 1
137+
if rtype == "hotfix" {
138+
patch = 2
139+
}
140+
r.Release = fmt.Sprintf("%s.%d-%s", time.Now().Format("20060102"), patch, target)
141+
// 获取当前用户
142+
user, err := currentUser()
143+
if err != nil {
144+
fmt.Fprintf(os.Stderr, "获取负责人失败, %v\n", err)
145+
return
146+
}
147+
r.Principal = user.Name
148+
// 过滤产品线特有服务
149+
var otherProductService []string
150+
if product == "enterprise-saas" {
151+
otherProductService = productOnlyServices["professional"]
152+
} else {
153+
otherProductService = productOnlyServices["enterprise-saas"]
154+
}
155+
ns := make([]string, 0)
156+
for _, s := range r.Services {
157+
if !contains(otherProductService, s) {
158+
ns = append(ns, s)
159+
}
160+
}
161+
r.Services = ns
162+
f := os.Stdout
163+
shouldSave := len(output) > 0
164+
if shouldSave {
165+
f, err = os.Create(output)
166+
}
167+
err = r.save(f)
168+
if err != nil {
169+
if shouldSave {
170+
fmt.Fprintf(os.Stderr, "文件保存失败, %v\n", err)
171+
} else {
172+
fmt.Fprintf(os.Stderr, "%v", err)
173+
}
174+
} else if shouldSave {
175+
fmt.Printf("版本发布 %s 已保存到 %s\n", r.Release, output)
176+
}
177+
},
178+
}
179+
180+
func contains(s interface{}, elem interface{}) bool {
181+
arrV := reflect.ValueOf(s)
182+
if arrV.Kind() == reflect.Slice {
183+
for i := 0; i < arrV.Len(); i++ {
184+
if arrV.Index(i).Interface() == elem {
185+
return true
186+
}
187+
}
188+
}
189+
return false
190+
}
191+
192+
type createRelease struct {
193+
Changes []changelog
194+
Diff string
195+
Hotfix bool
196+
Principal string
197+
Milestone string
198+
Services []string
199+
Release string
200+
Migration []migration
201+
Master string
202+
}
203+
204+
var funcMap = template.FuncMap{
205+
"inc": func(i int) int {
206+
return i + 1
120207
},
121208
}
122209

210+
func (release *createRelease) save(o io.Writer) error {
211+
outputTpl, err := template.New("output").Funcs(funcMap).Parse(`## ChangeLog
212+
213+
{{range .Changes}}- {{.Title}} #{{.MergeID}}
214+
{{end}}
215+
216+
## Diff
217+
218+
{{.Diff}}
219+
220+
## Checklist
221+
222+
### 发布类型
223+
224+
{{if .Hotfix}}Hotfix{{else}}常规更新{{end}}
225+
226+
### 负责人
227+
228+
@{{.Principal}}
229+
230+
### 版本规划
231+
232+
{{if .Milestone}}{{.Milestone}}{{else}}无{{end}}
233+
234+
### 发布服务
235+
236+
| 应用名称 | 发布镜像 | 执行顺序 |
237+
| ---------- | ---------- | ---------- |
238+
{{range $index, $element := .Services}}| {{$element}} | {{$.Release}} | {{(inc $index)}} |
239+
{{end}}
240+
241+
### 服务配置修改
242+
243+
{{range .Migration}}{{.ScriptName}}
244+
` + "`" + "`" + "`" + `
245+
{{.Script}}
246+
` + "`" + "`" + "`" + `
247+
{{end}}
248+
249+
### 发布后 master 指向
250+
251+
` + "`" + "`" + "`" + `
252+
{{.Master}}
253+
` + "`" + "`" + "`" + `
254+
`)
255+
256+
if err != nil {
257+
return fmt.Errorf("构建输出文件模板失败, %v", err)
258+
}
259+
260+
err = outputTpl.Execute(o, release)
261+
if err != nil {
262+
return fmt.Errorf("执行模板失败, %v", err)
263+
}
264+
265+
return nil
266+
267+
}
268+
269+
func currentUser() (*model.User, error) {
270+
req := request.NewGet(currentUserURI)
271+
user := model.User{}
272+
err := req.SendAndUnmarshal(&user)
273+
if err != nil {
274+
return nil, fmt.Errorf("获取当前用户失败, %v", err)
275+
}
276+
return &user, nil
277+
}
278+
123279
// release 主要包含以下五个部分
124-
// 1. 变更记录(Changelog
280+
// 1. 变更记录(changelog
125281
// 2. 版本对比(Diff)
126282
// 3. 发布服务列表
127283
// 4. 服务配置更新
128-
func release(src string, target string) (err error) {
129-
// Ref to Commit ID
284+
func release(src string, target string) (r *createRelease, err error) {
285+
// ref to Commit ID
130286
s := src
131287
t := target
288+
r = &createRelease{}
132289
if len(src) != 40 {
133290
s, err = commitID(src)
134291
}
292+
r.Master = s
293+
135294
if err != nil {
136-
return err
295+
return nil, err
137296
}
138297
if len(target) != 40 {
139298
t, err = commitID(target)
140299
}
141300
if err != nil {
142-
return err
301+
return nil, err
143302
}
144-
fmt.Printf("compare: %s(%s)...%s(%s)\n\n", s, src, t, target)
145303

146-
// Changelog
304+
// 变更记录
147305
d, err := diff(s, t)
148306
if err != nil {
149-
return err
307+
return nil, err
150308
}
151-
changes, err := changelog(d.Commits)
309+
r.Changes, err = changelogs(d.Commits)
152310
if err != nil {
153-
return err
154-
}
155-
size := len(changes)
156-
for i, c := range changes {
157-
fmt.Printf("changelog %d/%d: %s #%d\n", i+1, size, c.Title, c.MergeID)
311+
return nil, err
158312
}
159313

160-
// Diff
161-
compareLink, err := compareURL(s, t)
314+
// 网页版本对比链接
315+
r.Diff, err = compareURL(s, t)
162316
if err != nil {
163-
return err
317+
return nil, err
164318
}
165-
fmt.Printf("\ncompare link %s\n\n", compareLink)
166319

167-
// Service from change files
320+
// 变更文件列表中提取需要更新的服务列表
168321
paths := d.DiffStat.Paths
169322
names := make([]string, 0)
170323
for _, path := range paths {
171324
names = append(names, path.Name)
172325
}
173-
fmt.Printf("\nservices %v\n\n", findServices(names))
326+
r.Services = findServices(names)
174327

175-
return nil
328+
// 不同合并请求中的迁移脚本
329+
r.Migration, err = migrationScripts(r.Changes)
330+
if err != nil {
331+
return nil, fmt.Errorf("获取迁移脚本失败,%v", err)
332+
}
333+
334+
return r, nil
335+
}
336+
337+
type migration struct {
338+
Services []string
339+
ScriptName string
340+
Script string
341+
}
342+
343+
func file(c model.Commit, n string) (string, error) {
344+
encodedParams := url.PathEscape(fmt.Sprintf("%s/%s", c.CommitID, n))
345+
req := request.NewGet(fmt.Sprintf(gitBlobURI, encodedParams))
346+
blob := model.Blob{}
347+
err := req.SendAndUnmarshal(&blob)
348+
if err != nil {
349+
return "", fmt.Errorf("获取文件内容失败, name: %s, %v", n, err)
350+
}
351+
return blob.File.Data, nil
352+
}
353+
354+
func migrationScripts(c []changelog) ([]migration, error) {
355+
scripts := make([]migration, 0)
356+
for _, log := range c {
357+
req := request.NewGet(fmt.Sprintf(mergeURI, log.MergeID))
358+
merge := model.Merge{}
359+
err := req.SendAndUnmarshal(&merge)
360+
if err != nil {
361+
return nil, fmt.Errorf("获取合并请求失败, mergeID: %d, %v", log.MergeID, err)
362+
}
363+
paths := merge.MergeRequest.DiffStat.Paths
364+
for _, path := range paths {
365+
if path.Deletions == 0 && path.Insertions > 0 {
366+
var migrationScripsPrefix = "e-coding/doc/mysql/migrate_script/migration"
367+
if strings.HasPrefix(path.Name, migrationScripsPrefix) {
368+
services := findServices([]string{path.Name})
369+
mrFirstCommit := merge.MergeRequest.Commits[0]
370+
script, err := file(mrFirstCommit, path.Name)
371+
if err != nil {
372+
return nil, fmt.Errorf("获取迁移脚本文件内容失败,mergeID: %d, %v", log.MergeID, err)
373+
}
374+
scripts = append(scripts, migration{Services: services, Script: script, ScriptName: path.Name})
375+
}
376+
}
377+
}
378+
}
379+
return scripts, nil
176380
}
177381

178382
func findServices(names []string) []string {
@@ -214,14 +418,14 @@ func matchPattern(file string, patterns []string) (int, error) {
214418
return -1, fmt.Errorf("无匹配结果, 文件不包含以下前缀\n\t文件:%s\n\t前缀:[%s]", file, strings.Join(patterns, ", "))
215419
}
216420

217-
// ChangeLog 包含 Merge Request 标题、Merge Request 完整信息以及任务完整信息
218-
type ChangeLog struct {
421+
// changelog 包含 Merge Request 标题、Merge Request 完整信息以及任务完整信息
422+
type changelog struct {
219423
Title string
220424
MergeID int
221425
}
222426

223-
func changelog(commits []model.Commit) ([]ChangeLog, error) {
224-
changelogs := make([]ChangeLog, 0)
427+
func changelogs(commits []model.Commit) ([]changelog, error) {
428+
changelogs := make([]changelog, 0)
225429
for _, commit := range commits {
226430
msg := commit.AllMessage
227431
messages := strings.Split(msg, "\n")
@@ -230,7 +434,7 @@ func changelog(commits []model.Commit) ([]ChangeLog, error) {
230434
continue
231435
}
232436
title = strings.Replace(title, "Merge Request: ", "", 1)
233-
c := ChangeLog{Title: title}
437+
c := changelog{Title: title}
234438
if mergeID != 0 {
235439
c.MergeID = mergeID
236440
}
@@ -239,8 +443,9 @@ func changelog(commits []model.Commit) ([]ChangeLog, error) {
239443
return changelogs, nil
240444
}
241445

446+
var mergeRequestIDReg = regexp.MustCompile(`merge/(\d+)`)
447+
242448
func mergeRequestID(msg string) int {
243-
mergeRequestIDReg := regexp.MustCompile(`merge/(\d+)`)
244449
matches := mergeRequestIDReg.FindStringSubmatch(msg)
245450
if len(matches) == 2 {
246451
id, err := strconv.Atoi(matches[1])
@@ -311,4 +516,7 @@ func defaultBranchCommitID() (branch string, err error) {
311516

312517
func init() {
313518
rootCmd.AddCommand(releaseCmd)
519+
releaseCmd.Flags().StringVarP(&output, "output", "o", "", "保存到文件")
520+
releaseCmd.Flags().StringVarP(&rtype, "type", "t", "normal", "发布类型,hotfix - 紧急修复或者 normal - 常规更新")
521+
releaseCmd.Flags().StringVarP(&product, "product", "p", "enterprise-saas", "产品线,enterprise-saas 或者 professional")
314522
}

0 commit comments

Comments
 (0)