@@ -2,16 +2,21 @@ package cmd
2
2
3
3
import (
4
4
"fmt"
5
+ "os"
5
6
"strconv"
6
7
"strings"
7
8
8
9
"github.com/MakeNowJust/heredoc/v2"
10
+ "github.com/fatih/color"
11
+ gitlab "gitlab.com/gitlab-org/api/client-go"
9
12
"github.com/pkg/errors"
10
13
"github.com/rsteube/carapace"
14
+ "github.com/savioxavier/termlink"
11
15
"github.com/spf13/cobra"
12
- gitlab "gitlab.com/gitlab-org/api/client-go"
13
16
"github.com/zaquestion/lab/internal/action"
17
+ "github.com/zaquestion/lab/internal/git"
14
18
lab "github.com/zaquestion/lab/internal/gitlab"
19
+ "golang.org/x/term"
15
20
)
16
21
17
22
var (
@@ -39,6 +44,119 @@ var (
39
44
mrReviewerID * gitlab.ReviewerIDValue
40
45
)
41
46
47
+ func truncateText (s string , length int ) (string ) {
48
+ if length > len (s ) {
49
+ return s
50
+ }
51
+ return s [:length ]
52
+ }
53
+
54
+ func printRED (text string , cols int ) {
55
+ fmt .Printf (color .RedString ("%-" + fmt .Sprintf ("%d" , cols )+ "s" , text ))
56
+ }
57
+
58
+ func printGREEN (text string , cols int ) {
59
+ fmt .Printf (color .GreenString ("%-" + fmt .Sprintf ("%d" , cols )+ "s" , text ))
60
+ }
61
+
62
+ func printYELLOW (text string , cols int ) {
63
+ fmt .Printf (color .YellowString ("%-" + fmt .Sprintf ("%d" , cols )+ "s" , text ))
64
+ }
65
+
66
+ func printColumns (data [][]string ) {
67
+ spacing := 2 // adjust this variable to increase/decrease spacing between columns
68
+
69
+ columnWidths := make ([]int , len (data [0 ]))
70
+ for _ , row := range data {
71
+ for cellnum , cell := range row {
72
+ if cellnum == 0 { // the 0th entry is the webURL and is not output
73
+ continue
74
+ }
75
+ if len (cell ) > columnWidths [cellnum ] {
76
+ columnWidths [cellnum ] = len (cell )
77
+ }
78
+ }
79
+ }
80
+
81
+ // Make sure the output fits on the screen. If it does not, then truncate
82
+ // the title field.
83
+
84
+ // get the screen resolution (only width is needed)
85
+ width , _ , err := term .GetSize (int (os .Stdin .Fd ()))
86
+ if err != nil {
87
+ log .Fatal (err )
88
+ }
89
+
90
+ // Determine the output string length
91
+ linelength := 0
92
+ for _ , col := range columnWidths {
93
+ linelength += col
94
+ }
95
+
96
+ // This is the actual line length. It is the columnWidths (calculated in the
97
+ // for loop above), extra spacing in the middle columns (ie, 2 * (# of cols - 2)
98
+ // and one extra character for the newline.
99
+ linelength = linelength + (spacing * (len (columnWidths ) - 2 ) + 1 )
100
+
101
+ // If the line length is greater than the width of the terminal, truncate
102
+ // the Title column. The title text itself is truncated in the switch statement below.
103
+ delta := linelength - width
104
+ if delta > 0 {
105
+ columnWidths [2 ] = columnWidths [2 ] - delta
106
+ }
107
+
108
+ // output the data to the screen
109
+ for rownum , row := range data {
110
+ weburl := row [0 ]
111
+ for cellnum , cell := range row {
112
+ if cellnum == 0 { // the 0th entry is the webURL and is not output
113
+ continue
114
+ }
115
+ if rownum == 0 { // print out the header
116
+ if cellnum < (len (row ) - 1 ) {
117
+ fmt .Printf ("%-*s" , columnWidths [cellnum ]+ spacing , cell )
118
+ } else { // no spaces after last column
119
+ fmt .Printf ("%-*s" , columnWidths [cellnum ], cell )
120
+ }
121
+ continue
122
+ }
123
+
124
+ switch cellnum {
125
+ case 1 : // MRID (and weburl link)
126
+ // Requires initial offset of width+spacing-len(cell)
127
+ link := termlink .Link (cell , weburl )
128
+ fmt .Printf ("%s%-" + fmt .Sprintf ("%d" , columnWidths [cellnum ]+ spacing - len (cell ))+ "s" ,link , "" )
129
+
130
+ case 2 : // MR Title
131
+ fmt .Printf ("%-*s" , columnWidths [cellnum ]+ spacing , truncateText (cell , columnWidths [cellnum ]))
132
+ case 3 : // CI Status
133
+ switch cell {
134
+ case "failed" :
135
+ printRED (cell , columnWidths [cellnum ]+ spacing )
136
+ case "cancelled" :
137
+ printRED (cell , columnWidths [cellnum ]+ spacing )
138
+ case "success" :
139
+ printGREEN (cell , columnWidths [cellnum ]+ spacing )
140
+ case "running" :
141
+ printYELLOW (cell , columnWidths [cellnum ]+ spacing )
142
+ default :
143
+ printGREEN (cell , columnWidths [cellnum ]+ spacing )
144
+ }
145
+
146
+ case 4 : // MR Status
147
+ // spacing is not added here as this is the last column
148
+ switch cell {
149
+ case "mergeable" :
150
+ printGREEN (cell , columnWidths [cellnum ])
151
+ default :
152
+ printRED (cell , columnWidths [cellnum ])
153
+ }
154
+ }
155
+ }
156
+ fmt .Println ()
157
+ }
158
+ }
159
+
42
160
// listCmd represents the list command
43
161
var listCmd = & cobra.Command {
44
162
Use : "list [remote] [search]" ,
@@ -62,9 +180,15 @@ var listCmd = &cobra.Command{
62
180
lab mr list --ready
63
181
lab mr list --no-conflicts
64
182
lab mr list -x 'test MR'
65
- lab mr list -r johndoe` ),
183
+ lab mr list -r johndoe
184
+ lab mr list --show-status` ),
66
185
PersistentPreRun : labPersistentPreRun ,
67
186
Run : func (cmd * cobra.Command , args []string ) {
187
+ rn , err := git .PathWithNamespace (defaultRemote )
188
+ if err != nil {
189
+ return
190
+ }
191
+
68
192
mrs , err := mrList (args )
69
193
if err != nil {
70
194
log .Fatal (err )
@@ -73,9 +197,82 @@ var listCmd = &cobra.Command{
73
197
pager := newPager (cmd .Flags ())
74
198
defer pager .Close ()
75
199
200
+ showstatus , _ := cmd .Flags ().GetBool ("show-status" )
201
+
202
+ if ! showstatus {
203
+ for _ , mr := range mrs {
204
+ fmt .Printf ("!%d %s\n " , mr .IID , mr .Title )
205
+ }
206
+ return
207
+ }
208
+
209
+ output := [][]string {{"" , "MRID" , "Title" , "CIStatus" , "MRStatus" }}
76
210
for _ , mr := range mrs {
77
- fmt .Printf ("!%d %s\n " , mr .IID , mr .Title )
211
+ mrx , err := lab .MRGet (rn , int (mr .IID ))
212
+ if err != nil {
213
+ log .Fatal (err )
214
+ }
215
+
216
+ // In general we use the Detailed Merge Status. There are some
217
+ // cases below where a custom status is used.
218
+ detailedMergeStatus := strings .Replace (mr .DetailedMergeStatus , "_" , " " , - 1 )
219
+
220
+ CIStatus := "no pipeline"
221
+ if mrx .HeadPipeline != nil {
222
+ CIStatus = mrx .HeadPipeline .Status
223
+ }
224
+
225
+ // Custom MR Status: If the status is success/not approved, then
226
+ // check to see if there are any threads that need to be resolved.
227
+ // If there are report 'unresolved threads' as a status
228
+ if detailedMergeStatus == "not approved" && CIStatus == "success" {
229
+ discussions , err := lab .MRListDiscussions (rn , mr .IID )
230
+ if err != nil {
231
+ log .Fatal (err )
232
+ }
233
+
234
+ totalresolved := 0
235
+ totalresolvable := 0
236
+ for _ , discussion := range discussions {
237
+ resolved := 0
238
+ resolvable := 0
239
+ for _ , note := range discussion .Notes {
240
+ if note .Resolved {
241
+ resolved ++
242
+ }
243
+ if note .Resolvable {
244
+ resolvable ++
245
+ }
246
+ }
247
+ if resolved != 0 {
248
+ totalresolved ++
249
+ }
250
+ if resolvable != 0 {
251
+ totalresolvable ++
252
+ }
253
+ }
254
+ if totalresolvable != 0 && totalresolvable != totalresolved {
255
+ detailedMergeStatus = fmt .Sprintf ("unresolved threads(%d/%d)" , totalresolved , totalresolvable )
256
+ }
257
+ }
258
+
259
+ // Custom Status: If the MR Status is 'not approved' also output
260
+ // the number of remaining approvals necessary.
261
+ if detailedMergeStatus == "not approved" {
262
+ approvals , err := lab .GetMRApprovalsConfiguration (rn , mr .IID )
263
+ if err != nil {
264
+ log .Fatal (err )
265
+ }
266
+ detailedMergeStatus = fmt .Sprintf ("%s(%d/%d)" , detailedMergeStatus , len (approvals .ApprovedBy ), approvals .ApprovalsRequired )
267
+ }
268
+ output = append (output ,
269
+ []string {mr .WebURL , // weburl (used to convert MRID to URL)
270
+ strconv .Itoa (mr .IID ), // MRID
271
+ mr .Title , // Title
272
+ CIStatus , // CI Status
273
+ detailedMergeStatus }) // MR Status
78
274
}
275
+ printColumns (output )
79
276
},
80
277
}
81
278
@@ -252,6 +449,8 @@ func init() {
252
449
listCmd .Flags ().BoolVarP (& mrExactMatch , "exact-match" , "x" , false , "match on the exact (case-insensitive) search terms" )
253
450
listCmd .Flags ().StringVar (
254
451
& mrReviewer , "reviewer" , "" , "list only MRs with reviewer set to $username/any/none" )
452
+ listCmd .Flags ().BoolP ("show-status" , "" , false , "show CI and MR status (slow on projects with large number of MRs)" )
453
+
255
454
256
455
mrCmd .AddCommand (listCmd )
257
456
carapace .Gen (listCmd ).FlagCompletion (carapace.ActionMap {
0 commit comments