@@ -16,11 +16,15 @@ package cmd
1616
1717import (
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
96112var 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 ("\n compare 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 ( " \n services %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
178382func 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+
242448func 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
312517func 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