@@ -16,11 +16,15 @@ package cmd
16
16
17
17
import (
18
18
"fmt"
19
+ "io"
19
20
"net/url"
20
21
"os"
22
+ "reflect"
21
23
"regexp"
22
24
"strconv"
23
25
"strings"
26
+ "text/template"
27
+ "time"
24
28
25
29
"e.coding.net/codingcorp/coding-cli/pkg/model"
26
30
@@ -32,6 +36,9 @@ const (
32
36
defaultBranchURI = "/api/user/codingcorp/project/coding-dev/git/branches/default"
33
37
commitDetailURI = "/api/user/codingcorp/project/coding-dev/git/commit/%s"
34
38
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"
35
42
diffTemplate = "/p/coding-dev/git/compare/%s...%s"
36
43
)
37
44
@@ -92,14 +99,22 @@ var services = [][]string{
92
99
{"coding" , "scheduler" },
93
100
}
94
101
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
+
95
111
// releaseCmd represents the release command
96
112
var releaseCmd = & cobra.Command {
97
113
Use : "release [分支、提交或标签]" ,
98
114
Short : "创建版本发布" ,
99
115
Long : `创建版本发布
100
116
101
- 为分支、提交或标签(简称 ref)创建版本发布,版本发布分为常规发布和紧急修复两类。
102
- 创建版本发布时,默认发布类型为常规更新,可通过 -t(--type) 指定类型。创建必须提供 ref 信息即可。` ,
117
+ 为分支、提交或标签(简称 ref)创建版本发布,版本发布分为常规发布和紧急修复两类。` ,
103
118
Args : cobra .MinimumNArgs (1 ),
104
119
Run : func (cmd * cobra.Command , args []string ) {
105
120
if len (args ) == 0 {
@@ -112,67 +127,256 @@ var releaseCmd = &cobra.Command{
112
127
fmt .Fprintf (os .Stderr , "无法获取默认分支, %v\n " , err )
113
128
return
114
129
}
115
- err = release (source , target )
130
+ r , err : = release (source , target )
116
131
if err != nil {
117
132
fmt .Fprintf (os .Stderr , "无法创建版本发布, %v\n " , err )
118
133
return
119
134
}
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
120
207
},
121
208
}
122
209
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
+
123
279
// release 主要包含以下五个部分
124
- // 1. 变更记录(Changelog )
280
+ // 1. 变更记录(changelog )
125
281
// 2. 版本对比(Diff)
126
282
// 3. 发布服务列表
127
283
// 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
130
286
s := src
131
287
t := target
288
+ r = & createRelease {}
132
289
if len (src ) != 40 {
133
290
s , err = commitID (src )
134
291
}
292
+ r .Master = s
293
+
135
294
if err != nil {
136
- return err
295
+ return nil , err
137
296
}
138
297
if len (target ) != 40 {
139
298
t , err = commitID (target )
140
299
}
141
300
if err != nil {
142
- return err
301
+ return nil , err
143
302
}
144
- fmt .Printf ("compare: %s(%s)...%s(%s)\n \n " , s , src , t , target )
145
303
146
- // Changelog
304
+ // 变更记录
147
305
d , err := diff (s , t )
148
306
if err != nil {
149
- return err
307
+ return nil , err
150
308
}
151
- changes , err := changelog (d .Commits )
309
+ r . Changes , err = changelogs (d .Commits )
152
310
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
158
312
}
159
313
160
- // Diff
161
- compareLink , err : = compareURL (s , t )
314
+ // 网页版本对比链接
315
+ r . Diff , err = compareURL (s , t )
162
316
if err != nil {
163
- return err
317
+ return nil , err
164
318
}
165
- fmt .Printf ("\n compare link %s\n \n " , compareLink )
166
319
167
- // Service from change files
320
+ // 变更文件列表中提取需要更新的服务列表
168
321
paths := d .DiffStat .Paths
169
322
names := make ([]string , 0 )
170
323
for _ , path := range paths {
171
324
names = append (names , path .Name )
172
325
}
173
- fmt . Printf ( " \n services %v \n \n " , findServices (names ) )
326
+ r . Services = findServices (names )
174
327
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
176
380
}
177
381
178
382
func findServices (names []string ) []string {
@@ -214,14 +418,14 @@ func matchPattern(file string, patterns []string) (int, error) {
214
418
return - 1 , fmt .Errorf ("无匹配结果, 文件不包含以下前缀\n \t 文件:%s\n \t 前缀:[%s]" , file , strings .Join (patterns , ", " ))
215
419
}
216
420
217
- // ChangeLog 包含 Merge Request 标题、Merge Request 完整信息以及任务完整信息
218
- type ChangeLog struct {
421
+ // changelog 包含 Merge Request 标题、Merge Request 完整信息以及任务完整信息
422
+ type changelog struct {
219
423
Title string
220
424
MergeID int
221
425
}
222
426
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 )
225
429
for _ , commit := range commits {
226
430
msg := commit .AllMessage
227
431
messages := strings .Split (msg , "\n " )
@@ -230,7 +434,7 @@ func changelog(commits []model.Commit) ([]ChangeLog, error) {
230
434
continue
231
435
}
232
436
title = strings .Replace (title , "Merge Request: " , "" , 1 )
233
- c := ChangeLog {Title : title }
437
+ c := changelog {Title : title }
234
438
if mergeID != 0 {
235
439
c .MergeID = mergeID
236
440
}
@@ -239,8 +443,9 @@ func changelog(commits []model.Commit) ([]ChangeLog, error) {
239
443
return changelogs , nil
240
444
}
241
445
446
+ var mergeRequestIDReg = regexp .MustCompile (`merge/(\d+)` )
447
+
242
448
func mergeRequestID (msg string ) int {
243
- mergeRequestIDReg := regexp .MustCompile (`merge/(\d+)` )
244
449
matches := mergeRequestIDReg .FindStringSubmatch (msg )
245
450
if len (matches ) == 2 {
246
451
id , err := strconv .Atoi (matches [1 ])
@@ -311,4 +516,7 @@ func defaultBranchCommitID() (branch string, err error) {
311
516
312
517
func init () {
313
518
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" )
314
522
}
0 commit comments