ruby-issues 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/issues +376 -159
- data/bin/todos +1 -0
- metadata +15 -22
- data/bin/todos +0 -551
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f6deed01a2a59cd3b896a927a85b51eae83ca73c
|
4
|
+
data.tar.gz: 1812367238a2c59990e949bbe4070ec5c217847b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9b26f17dbae9bfa96b8316fba969aa06366455678addce3bd383e23f6bc18862639ff031d8041b2a69ceae0a6816cdf2a5bfac775817731ba76f19a8b6ba945b
|
7
|
+
data.tar.gz: cad3041f6a80b68ecf71e0f8d4048ce552cbc57b34cea295191dd7a0cdfa921e60a813c45e8a4a9ff79f084233a152a0c6a3dfb042a96ebfa2d48bfcc5a3f2e9
|
data/bin/issues
CHANGED
@@ -1,30 +1,56 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# encoding: utf-8
|
3
|
-
|
4
|
-
|
3
|
+
#===========================================================================================
|
5
4
|
require 'rubygems'
|
6
5
|
require 'fattr'
|
7
6
|
require 'io/console'
|
8
|
-
require '
|
7
|
+
require 'securerandom'
|
9
8
|
require 'PrettyComment'
|
10
9
|
require 'trollop'
|
11
|
-
require 'tempfile'
|
12
10
|
require 'yaml'
|
11
|
+
require 'git'
|
13
12
|
|
14
13
|
#YAML::ENGINE.yamler = 'psych'
|
15
14
|
|
16
|
-
|
15
|
+
#===========================================================================================
|
17
16
|
|
17
|
+
def git_get_current_branch()
|
18
|
+
current_branch = `git branch`.split(/\n/).find{ |l| l.start_with?("*") }
|
19
|
+
|
20
|
+
if current_branch
|
21
|
+
current_branch =~ /\*\s+(.+)/
|
22
|
+
$~[1]
|
23
|
+
end
|
24
|
+
end
|
18
25
|
|
26
|
+
#-------------------------------------------------------------------------------------------
|
27
|
+
|
28
|
+
def is_issue_branch()
|
29
|
+
(git_get_current_branch() =~ /(feature|improvement|bug|task)\/[a-f0-9]{6}/) != nil
|
30
|
+
end
|
19
31
|
|
20
|
-
|
32
|
+
#-------------------------------------------------------------------------------------------
|
33
|
+
|
34
|
+
def max(a,b)
|
35
|
+
a > b ? a : b
|
36
|
+
end
|
37
|
+
|
38
|
+
#-------------------------------------------------------------------------------------------
|
39
|
+
|
40
|
+
def edit_string(aString)
|
41
|
+
tempfile_name = "/tmp/issues-" << SecureRandom.hex.force_encoding("UTF-8")
|
42
|
+
File.write(tempfile_name, aString)
|
43
|
+
system("$EDITOR #{tempfile_name}")
|
44
|
+
new_string = File.read(tempfile_name)
|
45
|
+
File.unlink(tempfile_name)
|
46
|
+
new_string
|
47
|
+
end
|
48
|
+
|
49
|
+
#===========================================================================================
|
21
50
|
|
22
51
|
class LogEntry
|
23
52
|
fattr :date, :message
|
24
53
|
|
25
|
-
|
26
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
27
|
-
|
28
54
|
def initialize(message)
|
29
55
|
@date = Time.new
|
30
56
|
@message = message.dup
|
@@ -32,21 +58,19 @@ class LogEntry
|
|
32
58
|
end
|
33
59
|
|
34
60
|
|
35
|
-
|
61
|
+
#===========================================================================================
|
36
62
|
|
37
63
|
class Issue
|
38
|
-
fattr :id, :created, :type, :title, :description, :status
|
64
|
+
fattr :id, :created, :type, :title, :description, :status, :milestone, :estimate
|
39
65
|
attr_accessor :history
|
40
66
|
|
41
|
-
|
42
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
67
|
+
#-------------------------------------------------------------------------------------------
|
43
68
|
|
44
69
|
def initialize
|
45
70
|
@history = []
|
46
71
|
end
|
47
72
|
|
48
|
-
|
49
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
73
|
+
#-------------------------------------------------------------------------------------------
|
50
74
|
|
51
75
|
def self.createNewIssue(title, type="bug")
|
52
76
|
newIssue = Issue.new
|
@@ -59,100 +83,103 @@ class Issue
|
|
59
83
|
newIssue
|
60
84
|
end
|
61
85
|
|
62
|
-
|
63
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
86
|
+
#-------------------------------------------------------------------------------------------
|
64
87
|
|
65
88
|
def copy_from(a_issue)
|
66
89
|
self.class.fattrs.each do |a|
|
67
|
-
|
90
|
+
value = a_issue.send(a)
|
91
|
+
new_value = value.is_a?(Numeric) || value == nil ? value : value.dup
|
92
|
+
a_issue.send(a) && self.send(a, new_value)
|
68
93
|
end
|
69
94
|
end
|
70
|
-
|
71
95
|
|
72
|
-
|
96
|
+
#-------------------------------------------------------------------------------------------
|
73
97
|
|
74
98
|
def log(message)
|
75
99
|
@history ||= []
|
76
100
|
@history << LogEntry.new(message)
|
77
101
|
end
|
78
102
|
|
79
|
-
|
80
|
-
|
81
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
103
|
+
#-------------------------------------------------------------------------------------------
|
82
104
|
|
83
|
-
def format_verbose
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
105
|
+
def format_verbose(opts)
|
106
|
+
milestone = @milestone ? "#{@milestone}" : "NO MILESTONE"
|
107
|
+
estimate = @estimate ? "#{@estimate}h" : "NO ESTIMATE"
|
108
|
+
milestone_estimate = "[#{estimate}, #{milestone}]"
|
109
|
+
|
110
|
+
result = []
|
111
|
+
result << PrettyComment.separator
|
112
|
+
result << PrettyComment.comment("#{@id[0,6]} #{@type.capitalize} (#{@status}) #{milestone_estimate} #{@created.to_s[0,16]}")
|
113
|
+
result << PrettyComment.comment("")
|
114
|
+
result << PrettyComment.format_line(@title, "#", false, "#")
|
88
115
|
|
89
116
|
if @description
|
90
|
-
|
117
|
+
result << PrettyComment.sub_heading("Description:")
|
91
118
|
@description.split("\n").each do |l|
|
92
|
-
|
119
|
+
result << PrettyComment.format_line(l, "#", false, "#")
|
93
120
|
end
|
94
121
|
end
|
95
122
|
|
96
123
|
if @history && @history.count > 0
|
97
|
-
|
98
|
-
@history.each { |l|
|
124
|
+
result << PrettyComment.sub_heading("Log:")
|
125
|
+
@history.each { |l| result << PrettyComment.format_line("#{l.message}", "# #{l.date.to_s[0,16]}", true, "#", "#") }
|
99
126
|
end
|
100
127
|
|
101
|
-
|
102
|
-
|
103
|
-
|
128
|
+
result << PrettyComment.separator
|
129
|
+
result << ""
|
130
|
+
result << ""
|
131
|
+
|
132
|
+
result.join("\n")
|
104
133
|
end
|
105
134
|
|
135
|
+
#-------------------------------------------------------------------------------------------
|
106
136
|
|
107
|
-
|
137
|
+
def format_list(opts)
|
138
|
+
entry = @title.dup
|
139
|
+
info_string = "#{short_id} (#{@type[0,1].capitalize})"
|
108
140
|
|
109
|
-
|
110
|
-
|
111
|
-
|
141
|
+
if opts[:list_milestone] == true
|
142
|
+
width = opts[:milestone_column_width]
|
143
|
+
info_string << " " << (@milestone ? @milestone.ljust(width) : "UNASSIGNED".ljust(width))
|
144
|
+
end
|
112
145
|
|
146
|
+
if opts[:list_estimate] == true
|
147
|
+
info_string << " " << (@estimate ? sprintf("%.2fh", @estimate).rjust(4) : " ")
|
148
|
+
end
|
113
149
|
|
114
|
-
|
150
|
+
#suffix = estimate_milestone.count > 0 ? "[#{estimate_milestone.join(' => ')}]" : ""
|
151
|
+
|
152
|
+
PrettyComment.format_line(entry, info_string, true)
|
153
|
+
end
|
115
154
|
|
116
|
-
|
117
|
-
file = Tempfile.new('issues')
|
118
|
-
file.write(@description)
|
119
|
-
file.close
|
120
|
-
system("$EDITOR #{file.path}")
|
155
|
+
#-------------------------------------------------------------------------------------------
|
121
156
|
|
122
|
-
|
123
|
-
new_description =
|
157
|
+
def edit_description
|
158
|
+
new_description = edit_string(@description)
|
124
159
|
|
125
160
|
if new_description != @description
|
126
161
|
@description = new_description.dup
|
127
162
|
return true
|
128
163
|
end
|
129
|
-
|
164
|
+
|
130
165
|
return false
|
131
166
|
end
|
132
167
|
|
133
|
-
|
134
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
168
|
+
#-------------------------------------------------------------------------------------------
|
135
169
|
|
136
170
|
def edit_all
|
137
|
-
|
138
|
-
|
139
|
-
file.close
|
171
|
+
original_yaml = self.to_yaml
|
172
|
+
new_yaml = edit_string(original_yaml)
|
140
173
|
|
141
|
-
|
142
|
-
|
143
|
-
file.open
|
144
|
-
|
145
|
-
if (file.read != self.to_yaml)
|
146
|
-
new_issue = YAML::load_file(file.path)
|
147
|
-
self.copy_from(new_issue)
|
174
|
+
if (new_yaml != original_yaml)
|
175
|
+
self.copy_from(YAML::parse(new_string))
|
148
176
|
return true
|
149
177
|
end
|
150
178
|
|
151
179
|
return false
|
152
180
|
end
|
153
181
|
|
154
|
-
|
155
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
182
|
+
#-------------------------------------------------------------------------------------------
|
156
183
|
|
157
184
|
def short_id
|
158
185
|
@id[0,6]
|
@@ -160,30 +187,27 @@ class Issue
|
|
160
187
|
|
161
188
|
end
|
162
189
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
#======================================================================================================================#
|
190
|
+
#===========================================================================================
|
191
|
+
# Main Program Logic
|
192
|
+
#===========================================================================================
|
167
193
|
|
168
194
|
class IssuesDb
|
169
195
|
fattr :issues_array
|
170
196
|
|
171
|
-
|
197
|
+
#-------------------------------------------------------------------------------------------
|
172
198
|
|
173
199
|
def initialize(database_file)
|
174
200
|
@database_file = database_file
|
175
201
|
@issues_array = FileTest.exists?(database_file) && YAML.load_file(database_file) || []
|
176
202
|
end
|
177
203
|
|
178
|
-
|
179
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
204
|
+
#-------------------------------------------------------------------------------------------
|
180
205
|
|
181
206
|
def select_issues(&select_proc)
|
182
207
|
return @issues_array.select(&select_proc)
|
183
208
|
end
|
184
209
|
|
185
|
-
|
186
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
210
|
+
#-------------------------------------------------------------------------------------------
|
187
211
|
|
188
212
|
def select_issue(&select_proc)
|
189
213
|
result = select_issues(&select_proc)
|
@@ -204,34 +228,32 @@ class IssuesDb
|
|
204
228
|
nil
|
205
229
|
end
|
206
230
|
|
207
|
-
|
208
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
231
|
+
#-------------------------------------------------------------------------------------------
|
209
232
|
|
210
233
|
def has_issue(issue_id)
|
211
234
|
@issues_array.any? { |issue| issue.id.start_with?(issue_id) }
|
212
235
|
end
|
213
236
|
|
214
|
-
|
215
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
237
|
+
#-------------------------------------------------------------------------------------------
|
216
238
|
|
217
239
|
def save_db()
|
218
240
|
FileTest.exists?('.issues') || Dir.mkdir('.issues')
|
219
241
|
File.open(@database_file, 'w' ) { |out| YAML.dump(@issues_array, out) }
|
220
242
|
end
|
221
243
|
|
222
|
-
|
223
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
244
|
+
#-------------------------------------------------------------------------------------------
|
224
245
|
|
225
246
|
def determine_issue_type(opts)
|
226
|
-
issue_types = %w{bug improvement task}
|
227
|
-
issue_type = nil
|
228
|
-
issue_types.each { |t| opts[t.to_sym] == true && issue_type = t }
|
247
|
+
issue_types = %w{bug improvement task feature}
|
248
|
+
issue_type = opts[:type] ? opts[:type].downcase : nil
|
229
249
|
|
230
250
|
issue_type && (return issue_type)
|
231
251
|
|
232
252
|
case opts[:title]
|
233
|
-
when /\b(improve
|
253
|
+
when /\b(improve)/i
|
234
254
|
"improvement"
|
255
|
+
when /\b(implement)/i
|
256
|
+
"feature"
|
235
257
|
when /\b(fix|bug|crash)/i
|
236
258
|
"bug"
|
237
259
|
else
|
@@ -239,47 +261,100 @@ class IssuesDb
|
|
239
261
|
end
|
240
262
|
end
|
241
263
|
|
242
|
-
|
243
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
264
|
+
#-------------------------------------------------------------------------------------------
|
244
265
|
|
245
266
|
def create_issue(opts)
|
246
267
|
type = determine_issue_type(opts)
|
247
268
|
new_issue = Issue.createNewIssue(opts[:title], type)
|
269
|
+
new_issue.milestone = opts[:milestone]
|
270
|
+
new_issue.estimate = opts[:estimate]
|
248
271
|
@issues_array << new_issue
|
249
272
|
save_db()
|
250
273
|
puts "Created issue #{new_issue.short_id} #{new_issue.title}"
|
251
274
|
end
|
252
275
|
|
276
|
+
#-------------------------------------------------------------------------------------------
|
277
|
+
|
278
|
+
def select_issues_with_opts(opts)
|
279
|
+
did_select_issue_types = opts[:type]
|
280
|
+
status_regex = opts[:all] ? /^(open|resolved|duplicate|wontfix)/ : /^open$/
|
281
|
+
did_select_milestone = opts[:milestone] != nil
|
282
|
+
milestone = opts[:milestone]
|
283
|
+
|
284
|
+
return @issues_array.select do |issue|
|
285
|
+
(status_regex =~ issue.status) \
|
286
|
+
&& (issue.type == opts[:type] || !did_select_issue_types) \
|
287
|
+
&& (issue.milestone == opts[:milestone] \
|
288
|
+
|| !did_select_milestone \
|
289
|
+
|| milestone.end_with?(".") && issue.milestone.start_with?(milestone)\
|
290
|
+
|| milestone.start_with?(".") && issue.milestone.end_with?(milestone)) \
|
291
|
+
&& (issue.milestone == nil || opts["no-milestone"] == false)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
#-------------------------------------------------------------------------------------------
|
296
|
+
|
297
|
+
def has_open_issues_with_milestone?
|
298
|
+
@issues_array.any? { |issue| issue.status == "open" && issue.milestone != nil }
|
299
|
+
end
|
300
|
+
|
301
|
+
#-------------------------------------------------------------------------------------------
|
302
|
+
|
303
|
+
def has_open_issues_with_estimate?
|
304
|
+
@issues_array.any? { |issue| issue.status == "open" && issue.estimate != nil }
|
305
|
+
end
|
306
|
+
|
307
|
+
#-------------------------------------------------------------------------------------------
|
308
|
+
|
309
|
+
def milestone_column_width(array = @issues_array)
|
310
|
+
result = 0
|
311
|
+
|
312
|
+
array.each { |issue| issue.milestone != nil && result = max(issue.milestone.length, result) }
|
313
|
+
|
314
|
+
result
|
315
|
+
end
|
316
|
+
|
317
|
+
#-------------------------------------------------------------------------------------------
|
318
|
+
|
319
|
+
def sort_none(a, b)
|
320
|
+
return 0
|
321
|
+
end
|
322
|
+
#-------------------------------------------------------------------------------------------
|
323
|
+
|
324
|
+
def sort_milestone(a, b)
|
325
|
+
return a.milestone <=> b.milestone
|
326
|
+
end
|
253
327
|
|
254
|
-
|
328
|
+
#-------------------------------------------------------------------------------------------
|
255
329
|
|
256
330
|
def list_issues(opts)
|
257
331
|
if opts[:issue_id]
|
258
|
-
list_issue(opts
|
332
|
+
list_issue(opts)
|
333
|
+
sort_proc = "sort_milestone"
|
259
334
|
else
|
260
335
|
list_proc = opts[:verbose] ? "format_verbose" : "format_list"
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
did_select_issue_types && issue_types.delete_if { |issue_type| opts["#{issue_type}s".to_sym] == false }
|
265
|
-
|
266
|
-
status_regex = opts[:all] ? /^(open|resolved|duplicate|wontfix)/ : /^open$/
|
267
|
-
|
268
|
-
issues = @issues_array.select { |issue| (status_regex =~ issue.status) && issue_types.include?(issue.type) }
|
269
|
-
issues.each {|issue| issue.method(list_proc).call}
|
336
|
+
select_issues_with_opts(opts).\
|
337
|
+
sort {|a,b| sort_milestone(a,b)}.\
|
338
|
+
each {|issue| puts issue.method(list_proc).call(opts)}
|
270
339
|
end
|
271
340
|
end
|
272
341
|
|
342
|
+
#-------------------------------------------------------------------------------------------
|
273
343
|
|
274
|
-
|
275
|
-
|
276
|
-
def list_issue(issue_id)
|
344
|
+
def list_issue(opts)
|
345
|
+
issue_id = opts[:issue_id]
|
277
346
|
issue = select_issue {|i| i.id.start_with?(issue_id) }
|
278
|
-
issue.format_verbose()
|
347
|
+
puts issue.format_verbose(opts)
|
279
348
|
end
|
280
349
|
|
350
|
+
#-------------------------------------------------------------------------------------------
|
351
|
+
|
352
|
+
def count_issues(opts)
|
353
|
+
num_issues = select_issues_with_opts(opts).count
|
354
|
+
puts "#{num_issues} issues."
|
355
|
+
end
|
281
356
|
|
282
|
-
|
357
|
+
#-------------------------------------------------------------------------------------------
|
283
358
|
|
284
359
|
def resolve_issues(opts)
|
285
360
|
issue_id = opts[:issue_id]
|
@@ -293,6 +368,8 @@ class IssuesDb
|
|
293
368
|
["resolved", "Resolved"]
|
294
369
|
when "wontfix"
|
295
370
|
["wontfix", "Won't fix"]
|
371
|
+
when "cantreproduce"
|
372
|
+
["cantreproduce", "Can't reproduce"]
|
296
373
|
when "duplicate"
|
297
374
|
["duplicate(#{duplicate_of_id})", "Duplicate"]
|
298
375
|
end
|
@@ -300,16 +377,25 @@ class IssuesDb
|
|
300
377
|
resolved_issue.status = status
|
301
378
|
resolved_issue.log "Changed status to #{status}"
|
302
379
|
|
303
|
-
message = "#{message} issue #{resolved_issue.short_id} #{resolved_issue.title}"
|
380
|
+
message = "#{message} issue #{resolved_issue.short_id}: #{resolved_issue.title}"
|
304
381
|
puts message
|
305
382
|
|
306
|
-
|
307
383
|
save_db()
|
308
|
-
opts[:commit] && exec("git commit -a -m \"#{message}\"")
|
309
|
-
end
|
310
384
|
|
385
|
+
if opts[:commit]
|
386
|
+
system "git add .issues"
|
387
|
+
system "git commit -m \"#{message}\""
|
388
|
+
end
|
389
|
+
|
390
|
+
if opts[:close_branch] == true
|
391
|
+
issue_branch = git_get_current_branch()
|
392
|
+
system "git checkout dev"
|
393
|
+
system "git merge #{issue_branch}"
|
394
|
+
system "git branch -d #{issue_branch}"
|
395
|
+
end
|
396
|
+
end
|
311
397
|
|
312
|
-
|
398
|
+
#-------------------------------------------------------------------------------------------
|
313
399
|
|
314
400
|
def delete_issues(opts)
|
315
401
|
delete_issues = []
|
@@ -339,8 +425,7 @@ class IssuesDb
|
|
339
425
|
|
340
426
|
end
|
341
427
|
|
342
|
-
|
343
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
428
|
+
#-------------------------------------------------------------------------------------------
|
344
429
|
|
345
430
|
def edit_issue(opts)
|
346
431
|
issue_id = opts[:issue_id]
|
@@ -357,35 +442,123 @@ class IssuesDb
|
|
357
442
|
did_change_issue && save_db()
|
358
443
|
end
|
359
444
|
|
445
|
+
#-------------------------------------------------------------------------------------------
|
446
|
+
|
447
|
+
def select_issue_for_estimate?(issue, milestone)
|
448
|
+
issue.milestone != nil \
|
449
|
+
&& issue.status == "open" \
|
450
|
+
&& (issue.milestone == milestone \
|
451
|
+
|| milestone.start_with?(".") && issue.milestone.end_with?(milestone) \
|
452
|
+
|| milestone.end_with?(".") && issue.milestone.start_with?(milestone) )
|
453
|
+
end
|
454
|
+
|
455
|
+
#-------------------------------------------------------------------------------------------
|
456
|
+
|
457
|
+
def get_milestones_for_pattern(pattern)
|
458
|
+
result_hash = {}
|
459
|
+
issues_array.each { |i| select_issue_for_estimate?(i, pattern) && result_hash[i.milestone] = true }
|
460
|
+
result_hash.keys.sort
|
461
|
+
end
|
462
|
+
|
463
|
+
#-------------------------------------------------------------------------------------------
|
360
464
|
|
361
|
-
|
465
|
+
def estimate(opts)
|
466
|
+
milestone = opts[:milestone]
|
467
|
+
total = 0.0
|
468
|
+
|
469
|
+
issues_to_estimate = @issues_array.select { |i| select_issue_for_estimate?(i, milestone) }
|
470
|
+
issues_without_estimates = @issues_array.select { |i| select_issue_for_estimate?(i, milestone) && !i.estimate }
|
471
|
+
|
472
|
+
if issues_to_estimate.empty?
|
473
|
+
puts "No open issues found for milestone " << milestone << "!"
|
474
|
+
exit
|
475
|
+
end
|
476
|
+
|
477
|
+
if issues_without_estimates.count > 0
|
478
|
+
puts PrettyComment.h3("Warning: Found issues without estimates")
|
479
|
+
issues_without_estimates.each {|i| puts i.format_list(opts)}
|
480
|
+
puts PrettyComment.separator("-")
|
481
|
+
puts
|
482
|
+
end
|
483
|
+
|
484
|
+
issues_to_estimate.each { |i| (i.estimate && total += i.estimate) }
|
485
|
+
|
486
|
+
milestones = get_milestones_for_pattern(milestone)
|
362
487
|
|
363
|
-
|
364
|
-
|
488
|
+
result_str = "Milestone" << (milestones.length > 1 ? "s" : "")
|
489
|
+
|
490
|
+
puts result_str << " #{milestones.join(", ")} will take #{total} man-hours to complete."
|
491
|
+
puts
|
492
|
+
end
|
493
|
+
|
494
|
+
#-------------------------------------------------------------------------------------------
|
495
|
+
|
496
|
+
def update(opts)
|
497
|
+
new_type = opts[:type]
|
498
|
+
new_milestone = opts[:milestone]
|
499
|
+
new_estimate = opts[:estimate]
|
500
|
+
|
365
501
|
opts[:issue_ids].each do |issue_id|
|
502
|
+
did_update = false
|
366
503
|
issue = select_issue { |i| i.id.start_with?(issue_id) }
|
367
|
-
|
368
|
-
|
369
|
-
issue.
|
370
|
-
|
371
|
-
|
372
|
-
|
504
|
+
|
505
|
+
if new_type
|
506
|
+
if issue.type != new_type
|
507
|
+
issue.type = new_type
|
508
|
+
issue.log("Changed type to #{issue.type}")
|
509
|
+
did_update = true
|
510
|
+
else
|
511
|
+
puts "Issue #{issue.short_id} already of type #{issue.type}."
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
if new_milestone
|
516
|
+
if issue.milestone != new_milestone
|
517
|
+
issue.milestone = new_milestone
|
518
|
+
issue.log("Changed milestone to #{issue.milestone}")
|
519
|
+
did_update = true
|
520
|
+
else
|
521
|
+
puts "Issue #{issue.short_id} already in milestone #{issue.milestone}."
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
if new_estimate
|
526
|
+
if issue.estimate != new_estimate
|
527
|
+
issue.estimate = new_estimate
|
528
|
+
issue.log("Changed estimate to #{issue.estimate}")
|
529
|
+
did_update = true
|
530
|
+
else
|
531
|
+
puts "Issue #{issue.short_id} already has estimate #{issue.estimate}h."
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
if did_update == true
|
536
|
+
puts issue.format_list(opts)
|
537
|
+
save_db()
|
373
538
|
end
|
374
539
|
end
|
375
|
-
|
376
|
-
save_db()
|
377
540
|
end
|
378
541
|
|
542
|
+
#-------------------------------------------------------------------------------------------
|
379
543
|
|
380
|
-
|
544
|
+
def start_branch(opts)
|
545
|
+
git_get_current_branch() == "dev" || Trollop::die("Must be on dev branch to start new issue branch.")
|
546
|
+
|
547
|
+
issue_id = opts[:issue_id]
|
548
|
+
issue = select_issue{|i| i.id.start_with?(issue_id) && i.status == "open"}
|
549
|
+
|
550
|
+
system("git checkout -b #{issue.type}/#{issue.short_id}")
|
551
|
+
end
|
552
|
+
|
553
|
+
#-------------------------------------------------------------------------------------------
|
381
554
|
|
382
555
|
|
383
556
|
end
|
384
557
|
|
385
558
|
|
386
|
-
|
559
|
+
#===========================================================================================
|
387
560
|
# Command Line Parsing
|
388
|
-
|
561
|
+
#===========================================================================================
|
389
562
|
|
390
563
|
def get_issue_ids(num_ids, usage)
|
391
564
|
result = []
|
@@ -404,21 +577,24 @@ def get_issue_ids(num_ids, usage)
|
|
404
577
|
end
|
405
578
|
end
|
406
579
|
|
407
|
-
|
408
|
-
#----------------------------------------------------------------------------------------------------------------------#
|
580
|
+
#-------------------------------------------------------------------------------------------
|
409
581
|
|
410
582
|
EXECUTABLE_NAME=File.basename($0)
|
411
583
|
DATABASE_NAME= ".issues/" << EXECUTABLE_NAME << ".yaml"
|
412
584
|
|
413
585
|
SUB_COMMANDS = {
|
414
|
-
"list"
|
415
|
-
"
|
416
|
-
"
|
417
|
-
"
|
418
|
-
"
|
419
|
-
"
|
420
|
-
"
|
421
|
-
"
|
586
|
+
"list" => "list issues",
|
587
|
+
"count" => "count issues",
|
588
|
+
"create" => "create a new issue",
|
589
|
+
"resolve" => "set status of issue to \"resolved\"",
|
590
|
+
"wontfix" => "set status of issue to \"won't fix\"",
|
591
|
+
"cantreproduce" => "set status of issue to \"can't reproduce\"",
|
592
|
+
"duplicate" => "mark issue as duplicate of another issue",
|
593
|
+
"edit" => "edit an existing issue",
|
594
|
+
"delete" => "delete an issue",
|
595
|
+
"estimate" => "calculate estimated man-hours for a milestone",
|
596
|
+
"update" => "update issue fields.",
|
597
|
+
"startbranch" => "start a new git branch with to work on issue"}
|
422
598
|
|
423
599
|
LeftFieldLength =
|
424
600
|
SUB_COMMANDS.collect { |key, value| key.length }.max
|
@@ -451,31 +627,33 @@ cmd ||= 'list'
|
|
451
627
|
|
452
628
|
cmd_opts = {}
|
453
629
|
|
454
|
-
if cmd == 'list'
|
630
|
+
if cmd == 'list' || cmd == 'count'
|
455
631
|
cmd_opts =
|
456
632
|
Trollop::options do
|
457
|
-
opt :all,
|
458
|
-
opt :newest, "
|
459
|
-
opt :oldest, "
|
460
|
-
opt :verbose, "verbose list of issues", :short => 'v'
|
461
|
-
opt :
|
462
|
-
opt :
|
463
|
-
opt
|
633
|
+
opt :all, "#{cmd} all issues", :short => 'a'
|
634
|
+
opt :newest, "#{cmd} newest issues first" if cmd == 'list'
|
635
|
+
opt :oldest, "#{cmd} oldest issues first" if cmd == 'list'
|
636
|
+
opt :verbose, "verbose list of issues", :short => 'v' if cmd == 'list'
|
637
|
+
opt :type, "#{cmd} issues of given type (bug, improvement, task, feature)", :short => 't', :type => String
|
638
|
+
opt :milestone, "#{cmd} issues for given milestone", :short => 'm', :type => String
|
639
|
+
opt "no-milestone", "#{cmd} all issues not assigned to any milestone"
|
464
640
|
end
|
465
641
|
|
466
|
-
|
642
|
+
if cmd == 'list'
|
643
|
+
ARGV.count > 0 && cmd_opts[:issue_id] = get_issue_ids(1, "list ID")
|
644
|
+
end
|
467
645
|
|
468
646
|
elsif cmd == "create"
|
469
647
|
cmd_opts =
|
470
648
|
Trollop::options do
|
471
|
-
opt :
|
472
|
-
opt :
|
473
|
-
opt :
|
649
|
+
opt :type, "create issue of specific type (e.g. bug, feature, improvement, task)", :short => 't', :type => String
|
650
|
+
opt :milestone, "specify the milestone of the new issue", :short => 'm', :type => String
|
651
|
+
opt :estimate, "specify the estimated effort", :short => 'e', :type => Float
|
474
652
|
end
|
475
653
|
cmd_opts[:title] = ARGV.shift || Trollop::die( "Please enter a title for the new issue!")
|
476
654
|
|
477
655
|
|
478
|
-
elsif cmd == "resolve" || cmd == "wontfix" || cmd == "duplicate"
|
656
|
+
elsif cmd == "resolve" || cmd == "wontfix" || cmd == "duplicate" || cmd == "cantreproduce"
|
479
657
|
cmd_opts =
|
480
658
|
Trollop::options do
|
481
659
|
opt :commit, "do a git commit", :short => 'c'
|
@@ -483,6 +661,10 @@ elsif cmd == "resolve" || cmd == "wontfix" || cmd == "duplicate"
|
|
483
661
|
|
484
662
|
if cmd == "duplicate"
|
485
663
|
cmd_opts[:issue_id], cmd_opts[:duplicate_of_id] = get_issue_ids(2, "duplicate ID(issue) ID(duplicate of)")
|
664
|
+
elsif cmd == "resolve" && is_issue_branch()
|
665
|
+
cmd_opts[:issue_id] = git_get_current_branch().split('/')[1]
|
666
|
+
cmd_opts[:close_branch] = true
|
667
|
+
cmd_opts[:commit] = true
|
486
668
|
else
|
487
669
|
cmd_opts[:issue_id] = get_issue_ids(1, "#{cmd} [-c] ID")
|
488
670
|
end
|
@@ -494,29 +676,56 @@ elsif cmd == "edit"
|
|
494
676
|
opt :description, "edit the issue description", :short => 'd'
|
495
677
|
end
|
496
678
|
cmd_opts[:issue_id] = get_issue_ids(1, "edit ID")
|
497
|
-
|
498
|
-
|
499
|
-
elsif cmd == "set-type"
|
679
|
+
|
680
|
+
elsif cmd == "estimate"
|
500
681
|
Trollop::options do
|
501
682
|
banner <<-EOL
|
502
683
|
Usage:
|
503
684
|
------
|
504
|
-
issues
|
685
|
+
issues estimate MILESTONE
|
686
|
+
|
687
|
+
Options:
|
688
|
+
--------
|
689
|
+
EOL
|
690
|
+
end
|
691
|
+
|
692
|
+
ARGV.count > 0 || Trollop::die("Usage: issues estmiate MILSTONE")
|
693
|
+
cmd_opts[:milestone] = ARGV.shift
|
694
|
+
|
695
|
+
elsif cmd == "update"
|
696
|
+
my_banner = <<-EOL
|
697
|
+
Usage:
|
698
|
+
------
|
699
|
+
issues update \
|
700
|
+
[--type (bug|improvement|task|feature)]\
|
701
|
+
[--milestone MILESTONE]\
|
702
|
+
[--estimate ESTIMATE]\
|
703
|
+
ID{,ID2,...}
|
505
704
|
|
506
705
|
Options:
|
507
706
|
--------
|
508
707
|
EOL
|
708
|
+
|
709
|
+
cmd_opts = Trollop::options do
|
710
|
+
banner my_banner
|
711
|
+
opt :type, "update the issue type", :short => 't', :type => String
|
712
|
+
opt :milestone, "update the milestone", :short => 'm', :type => String
|
713
|
+
opt :estimate, "update the estimate", :short => 'e', :type => Float
|
509
714
|
end
|
510
715
|
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
ARGV.count > 0 && cmd_opts[:issue_ids] = get_issue_ids(-1, "set-type {bug|improvement|task} ID")
|
716
|
+
cmd_opts[:type] && (%w{bug improvement task feature}.include?(cmd_opts[:type]) || Trollop::die("Please specify one of [bug, improvement, task, freature] as new issue type"))
|
717
|
+
cmd_opts[:type] || cmd_opts[:milestone] || cmd_opts[:estimate] || Trollop::die("You must specify one of (--type, --milestone, --estimate)")
|
718
|
+
|
719
|
+
ARGV.count > 0 && cmd_opts[:issue_ids] = get_issue_ids(-1, "set-type {bug|improvement|task|feature} ID")
|
515
720
|
|
516
721
|
|
517
722
|
elsif cmd == "delete"
|
518
723
|
cmd_opts[:issue_ids] = get_issue_ids(-1, "#{cmd} ID")
|
519
724
|
|
725
|
+
|
726
|
+
elsif cmd == "startbranch"
|
727
|
+
cmd_opts[:issue_id] = get_issue_ids(1, "list ID") || Trollop::die( "Please enter an id for the issue to work on in the new branch!")
|
728
|
+
|
520
729
|
else
|
521
730
|
Trollop::die "unknown command #{cmd.inspect}"
|
522
731
|
end
|
@@ -525,27 +734,35 @@ end
|
|
525
734
|
cmd_opts[:cmd] = cmd
|
526
735
|
|
527
736
|
|
528
|
-
|
529
|
-
#
|
530
|
-
|
737
|
+
#===========================================================================================
|
738
|
+
# Main
|
739
|
+
#===========================================================================================
|
531
740
|
|
532
741
|
Issues = IssuesDb.new(DATABASE_NAME)
|
742
|
+
cmd_opts[:list_estimate] = Issues.has_open_issues_with_estimate?()
|
743
|
+
cmd_opts[:milestone_column_width] = Issues.milestone_column_width()
|
744
|
+
cmd_opts[:list_milestone] = Issues.has_open_issues_with_milestone?()
|
533
745
|
|
534
746
|
case cmd
|
535
747
|
when "create"
|
536
748
|
Issues.create_issue(cmd_opts)
|
537
749
|
when "list"
|
538
750
|
Issues.list_issues(cmd_opts)
|
539
|
-
when "
|
751
|
+
when "count"
|
752
|
+
Issues.count_issues(cmd_opts)
|
753
|
+
when "resolve", "wontfix", "duplicate", "cantreproduce"
|
540
754
|
Issues.resolve_issues(cmd_opts)
|
541
755
|
when "edit"
|
542
756
|
Issues.edit_issue(cmd_opts)
|
543
|
-
when "
|
544
|
-
Issues.
|
757
|
+
when "estimate"
|
758
|
+
Issues.estimate(cmd_opts)
|
759
|
+
when "update"
|
760
|
+
Issues.update(cmd_opts)
|
545
761
|
when "delete"
|
546
762
|
Issues.delete_issues(cmd_opts)
|
763
|
+
when "startbranch"
|
764
|
+
Issues.start_branch(cmd_opts)
|
547
765
|
end
|
548
766
|
|
549
|
-
|
550
|
-
#======================================================================================================================#
|
767
|
+
#===========================================================================================
|
551
768
|
|