ghi 0.9.0.dev1 → 0.9.0.20120627
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.
- data/bin/ghi +1 -1
- data/lib/ghi.rb +46 -19
- data/lib/ghi/authorization.rb +11 -15
- data/lib/ghi/client.rb +2 -2
- data/lib/ghi/commands/close.rb +6 -1
- data/lib/ghi/commands/command.rb +24 -15
- data/lib/ghi/commands/comment.rb +39 -15
- data/lib/ghi/commands/config.rb +24 -4
- data/lib/ghi/commands/edit.rb +89 -22
- data/lib/ghi/commands/label.rb +1 -1
- data/lib/ghi/commands/list.rb +33 -22
- data/lib/ghi/commands/milestone.rb +78 -21
- data/lib/ghi/commands/open.rb +39 -14
- data/lib/ghi/commands/show.rb +16 -8
- data/lib/ghi/commands/version.rb +1 -1
- data/lib/ghi/editor.rb +39 -17
- data/lib/ghi/formatting.rb +125 -40
- data/lib/ghi/formatting/colors.rb +1 -1
- data/lib/ghi/web.rb +26 -0
- metadata +57 -48
data/lib/ghi/commands/edit.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module GHI
|
2
2
|
module Commands
|
3
3
|
class Edit < Command
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :editor
|
5
5
|
|
6
6
|
def options
|
7
7
|
OptionParser.new do |opts|
|
@@ -12,7 +12,7 @@ EOF
|
|
12
12
|
opts.on(
|
13
13
|
'-m', '--message [<text>]', 'change issue description'
|
14
14
|
) do |text|
|
15
|
-
next self.
|
15
|
+
next self.editor = true if text.nil?
|
16
16
|
assigns[:title], assigns[:body] = text.split(/\n+/, 2)
|
17
17
|
end
|
18
18
|
opts.on(
|
@@ -20,6 +20,9 @@ EOF
|
|
20
20
|
) do |assignee|
|
21
21
|
assigns[:assignee] = assignee
|
22
22
|
end
|
23
|
+
opts.on '--claim', 'assign to yourself' do
|
24
|
+
assigns[:assignee] = Authorization.username
|
25
|
+
end
|
23
26
|
opts.on(
|
24
27
|
'-s', '--state <in>', %w(open closed),
|
25
28
|
{'o'=>'open', 'c'=>'closed'}, "'open' or 'closed'"
|
@@ -34,36 +37,100 @@ EOF
|
|
34
37
|
opts.on(
|
35
38
|
'-L', '--label <labelname>...', Array, 'associate with label(s)'
|
36
39
|
) do |labels|
|
37
|
-
assigns[:labels]
|
40
|
+
(assigns[:labels] ||= []).concat labels
|
41
|
+
end
|
42
|
+
opts.separator ''
|
43
|
+
opts.separator 'Pull request options'
|
44
|
+
opts.on(
|
45
|
+
'-H', '--head [[<user>:]<branch>]',
|
46
|
+
'branch where your changes are implemented',
|
47
|
+
'(defaults to current branch)'
|
48
|
+
) do |head|
|
49
|
+
self.action = 'pull'
|
50
|
+
assigns[:head] = head
|
51
|
+
end
|
52
|
+
opts.on(
|
53
|
+
'-b', '--base [<branch>]',
|
54
|
+
'branch you want your changes pulled into', '(defaults to master)'
|
55
|
+
) do |base|
|
56
|
+
self.action = 'pull'
|
57
|
+
assigns[:base] = base
|
38
58
|
end
|
39
59
|
opts.separator ''
|
40
60
|
end
|
41
61
|
end
|
42
62
|
|
43
63
|
def execute
|
44
|
-
|
64
|
+
self.action = 'edit'
|
45
65
|
require_repo
|
66
|
+
require_issue
|
46
67
|
options.parse! args
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
68
|
+
case action
|
69
|
+
when 'edit'
|
70
|
+
begin
|
71
|
+
if editor || assigns.empty?
|
72
|
+
i = throb { api.get "/repos/#{repo}/issues/#{issue}" }.body
|
73
|
+
e = Editor.new "GHI_ISSUE_#{issue}"
|
74
|
+
message = e.gets format_editor(i)
|
75
|
+
e.unlink "There's no issue." if message.nil? || message.empty?
|
76
|
+
assigns[:title], assigns[:body] = message.split(/\n+/, 2)
|
77
|
+
end
|
78
|
+
if assigns[:title] && i
|
79
|
+
titles_match = assigns[:title].strip == i['title'].strip
|
80
|
+
if assigns[:body]
|
81
|
+
bodies_match = assigns[:body].to_s.strip == i['body'].to_s.strip
|
82
|
+
end
|
83
|
+
if titles_match && bodies_match
|
84
|
+
e.unlink if e
|
85
|
+
abort 'No change.' if assigns.dup.delete_if { |k, v|
|
86
|
+
[:title, :body].include? k
|
87
|
+
}
|
88
|
+
end
|
89
|
+
i = throb {
|
90
|
+
api.patch "/repos/#{repo}/issues/#{issue}", assigns
|
91
|
+
}.body
|
92
|
+
puts format_issue(i)
|
93
|
+
puts 'Updated.'
|
94
|
+
end
|
95
|
+
e.unlink if e
|
96
|
+
rescue Client::Error => e
|
97
|
+
raise unless error = e.errors.first
|
98
|
+
abort "%s %s %s %s." % [
|
99
|
+
error['resource'],
|
100
|
+
error['field'],
|
101
|
+
[*error['value']].join(', '),
|
102
|
+
error['code']
|
103
|
+
]
|
104
|
+
end
|
105
|
+
when 'pull'
|
106
|
+
begin
|
107
|
+
assigns[:issue] = issue
|
108
|
+
assigns[:base] ||= 'master'
|
109
|
+
head = begin
|
110
|
+
if ref = %x{
|
111
|
+
git rev-parse --abbrev-ref HEAD@{upstream} 2>/dev/null
|
112
|
+
}.chomp!
|
113
|
+
ref.split('/').last if $? == 0
|
114
|
+
end
|
115
|
+
end
|
116
|
+
assigns[:head] ||= head
|
117
|
+
if assigns[:head]
|
118
|
+
assigns[:head].sub!(/:$/, ":#{head}")
|
119
|
+
else
|
120
|
+
abort <<EOF.chomp
|
121
|
+
fatal: HEAD can't be null. (Is your current branch being tracked upstream?)
|
122
|
+
EOF
|
123
|
+
end
|
124
|
+
throb { api.post "/repos/#{repo}/pulls", assigns }
|
125
|
+
base = [repo.split('/').first, assigns[:base]].join ':'
|
126
|
+
puts 'Issue #%d set up to track remote branch %s against %s.' % [
|
127
|
+
issue, assigns[:head], base
|
128
|
+
]
|
129
|
+
rescue Client::Error => e
|
130
|
+
raise unless error = e.errors.last
|
131
|
+
abort error['message'].sub /^base /, ''
|
57
132
|
end
|
58
133
|
end
|
59
|
-
if titles_match && bodies_match
|
60
|
-
abort 'No change.' if assigns.dup.delete_if { |k, v|
|
61
|
-
[:title, :body].include? k
|
62
|
-
}
|
63
|
-
end
|
64
|
-
i = throb { api.patch "/repos/#{repo}/issues/#{issue}", assigns }.body
|
65
|
-
puts format_issue(i)
|
66
|
-
puts 'Updated.'
|
67
134
|
end
|
68
135
|
end
|
69
136
|
end
|
data/lib/ghi/commands/label.rb
CHANGED
data/lib/ghi/commands/list.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
require 'curses'
|
2
1
|
require 'date'
|
3
2
|
|
4
3
|
module GHI
|
5
4
|
module Commands
|
6
5
|
class List < Command
|
6
|
+
attr_accessor :web
|
7
7
|
attr_accessor :reverse
|
8
8
|
attr_accessor :quiet
|
9
9
|
|
@@ -23,7 +23,7 @@ module GHI
|
|
23
23
|
opts.on(
|
24
24
|
'-L', '--label <labelname>...', Array, 'by label(s)'
|
25
25
|
) do |labels|
|
26
|
-
assigns[:labels]
|
26
|
+
(assigns[:labels] ||= []).concat labels
|
27
27
|
end
|
28
28
|
opts.on(
|
29
29
|
'-S', '--sort <by>', %w(created updated comments),
|
@@ -45,9 +45,8 @@ module GHI
|
|
45
45
|
raise OptionParser::InvalidArgument, e.message
|
46
46
|
end
|
47
47
|
end
|
48
|
-
opts.on
|
49
|
-
|
50
|
-
end
|
48
|
+
opts.on('-v', '--verbose') { self.verbose = true }
|
49
|
+
opts.on('-w', '--web') { self.web = true }
|
51
50
|
opts.separator ''
|
52
51
|
opts.separator 'Global options'
|
53
52
|
opts.on(
|
@@ -69,13 +68,14 @@ module GHI
|
|
69
68
|
opts.on(
|
70
69
|
'-u', '--[no-]assignee [<user>]', 'assigned to specified user'
|
71
70
|
) do |assignee|
|
71
|
+
assignee = assignee.sub /^@/, ''
|
72
72
|
assigns[:assignee] = any_or_none_or assignee
|
73
73
|
end
|
74
74
|
opts.on '--mine', 'assigned to you' do
|
75
75
|
assigns[:assignee] = Authorization.username
|
76
76
|
end
|
77
77
|
opts.on(
|
78
|
-
'-U', '--mentioned [<user>]', 'mentioning you or specified user'
|
78
|
+
'-U', '--mentioned [<user>]', 'mentioning you or specified user'
|
79
79
|
) do |mentioned|
|
80
80
|
assigns[:mentioned] = mentioned || Authorization.username
|
81
81
|
end
|
@@ -84,32 +84,43 @@ module GHI
|
|
84
84
|
end
|
85
85
|
|
86
86
|
def execute
|
87
|
+
if index = args.index { |arg| /^@/ === arg }
|
88
|
+
assigns[:assignee] = args.delete_at(index)[1..-1]
|
89
|
+
end
|
90
|
+
|
87
91
|
begin
|
88
92
|
options.parse! args
|
89
93
|
rescue OptionParser::InvalidOption => e
|
90
94
|
fallback.parse! e.args
|
91
95
|
retry
|
92
96
|
end
|
97
|
+
assigns[:labels] = assigns[:labels].join ',' if assigns[:labels]
|
93
98
|
if reverse
|
94
99
|
assigns[:sort] ||= 'created'
|
95
100
|
assigns[:direction] = 'asc'
|
96
101
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
102
|
+
if web
|
103
|
+
Web.new(repo || 'dashboard').open 'issues', assigns
|
104
|
+
else
|
105
|
+
assigns[:per_page] = 100
|
106
|
+
unless quiet
|
107
|
+
print header = format_issues_header
|
108
|
+
print "\n" unless paginate?
|
109
|
+
end
|
110
|
+
res = throb(
|
111
|
+
0, format_state(assigns[:state], quiet ? CURSOR[:up][1] : '#')
|
112
|
+
) { api.get uri, assigns }
|
113
|
+
print "\r#{CURSOR[:up][1]}" if header && paginate?
|
114
|
+
page header do
|
115
|
+
issues = res.body
|
116
|
+
if verbose
|
117
|
+
puts issues.map { |i| format_issue i }
|
118
|
+
else
|
119
|
+
puts format_issues(issues, repo.nil?)
|
120
|
+
end
|
121
|
+
break unless res.next_page
|
122
|
+
res = throb { api.get res.next_page }
|
110
123
|
end
|
111
|
-
break unless res.next_page
|
112
|
-
res = throb { api.get res.next_page }
|
113
124
|
end
|
114
125
|
rescue Client::Error => e
|
115
126
|
if e.response.code == '422'
|
@@ -124,7 +135,7 @@ module GHI
|
|
124
135
|
private
|
125
136
|
|
126
137
|
def uri
|
127
|
-
repo ? "/repos/#{repo}
|
138
|
+
(repo ? "/repos/#{repo}" : '') << '/issues'
|
128
139
|
end
|
129
140
|
|
130
141
|
def fallback
|
@@ -3,7 +3,9 @@ require 'date'
|
|
3
3
|
module GHI
|
4
4
|
module Commands
|
5
5
|
class Milestone < Command
|
6
|
+
attr_accessor :edit
|
6
7
|
attr_accessor :reverse
|
8
|
+
attr_accessor :web
|
7
9
|
|
8
10
|
#--
|
9
11
|
# FIXME: Opt for better interface, e.g.,
|
@@ -39,12 +41,15 @@ EOF
|
|
39
41
|
opts.on '-v', '--verbose', 'list milestones verbosely' do
|
40
42
|
self.verbose = true
|
41
43
|
end
|
44
|
+
opts.on('-w', '--web') { self.web = true }
|
42
45
|
opts.separator ''
|
43
46
|
opts.separator 'Milestone modification options'
|
44
47
|
opts.on(
|
45
|
-
'-m', '--message <text>', 'change milestone description'
|
48
|
+
'-m', '--message [<text>]', 'change milestone description'
|
46
49
|
) do |text|
|
47
50
|
self.action = 'create'
|
51
|
+
self.edit = true
|
52
|
+
next unless text
|
48
53
|
assigns[:title], assigns[:description] = text.split(/\n+/, 2)
|
49
54
|
end
|
50
55
|
# FIXME: We already describe --[no-]closed; describe this, too?
|
@@ -76,6 +81,7 @@ EOF
|
|
76
81
|
|
77
82
|
def execute
|
78
83
|
self.action = 'index'
|
84
|
+
require_repo
|
79
85
|
extract_milestone
|
80
86
|
|
81
87
|
begin
|
@@ -96,32 +102,83 @@ EOF
|
|
96
102
|
|
97
103
|
case action
|
98
104
|
when 'index'
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
105
|
+
if web
|
106
|
+
Web.new(repo).open 'issues/milestones', assigns
|
107
|
+
else
|
108
|
+
assigns[:per_page] = 100
|
109
|
+
state = assigns[:state] || 'open'
|
110
|
+
print format_state state, "# #{repo} #{state} milestones"
|
111
|
+
print "\n" unless paginate?
|
112
|
+
res = throb(0, format_state(state, '#')) { api.get uri, assigns }
|
113
|
+
page do
|
114
|
+
milestones = res.body
|
115
|
+
if verbose
|
116
|
+
puts milestones.map { |m| format_milestone m }
|
117
|
+
else
|
118
|
+
puts format_milestones(milestones)
|
119
|
+
end
|
120
|
+
break unless res.next_page
|
121
|
+
res = throb { api.get res.next_page }
|
109
122
|
end
|
110
|
-
break unless res.next_page
|
111
|
-
res = throb { api.get res.next_page }
|
112
123
|
end
|
113
124
|
when 'show'
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
125
|
+
if web
|
126
|
+
List.execute %W(-w -M #{milestone} -- #{repo})
|
127
|
+
else
|
128
|
+
m = throb { api.get uri }.body
|
129
|
+
page do
|
130
|
+
puts format_milestone(m)
|
131
|
+
puts 'Issues:'
|
132
|
+
args.unshift(*%W(-q -M #{milestone} -- #{repo}))
|
133
|
+
args.unshift '-v' if verbose
|
134
|
+
List.execute args
|
135
|
+
break
|
136
|
+
end
|
137
|
+
end
|
118
138
|
when 'create'
|
119
|
-
|
120
|
-
|
139
|
+
if web
|
140
|
+
Web.new(repo).open 'issues/milestones/new'
|
141
|
+
else
|
142
|
+
if assigns[:title].nil?
|
143
|
+
e = Editor.new 'GHI_MILESTONE'
|
144
|
+
message = e.gets format_milestone_editor
|
145
|
+
e.unlink 'Empty milestone.' if message.nil? || message.empty?
|
146
|
+
assigns[:title], assigns[:description] = message.split(/\n+/, 2)
|
147
|
+
end
|
148
|
+
m = throb { api.post uri, assigns }.body
|
149
|
+
puts 'Milestone #%d created.' % m['number']
|
150
|
+
e.unlink if e
|
151
|
+
end
|
121
152
|
when 'update'
|
122
|
-
|
123
|
-
|
153
|
+
if web
|
154
|
+
Web.new(repo).open "issues/milestones/#{milestone}/edit"
|
155
|
+
else
|
156
|
+
if edit || assigns.empty?
|
157
|
+
m = throb { api.get "/repos/#{repo}/milestones/#{milestone}" }.body
|
158
|
+
e = Editor.new "GHI_MILESTONE_#{milestone}"
|
159
|
+
message = e.gets format_milestone_editor(m)
|
160
|
+
e.unlink 'Empty milestone.' if message.nil? || message.empty?
|
161
|
+
assigns[:title], assigns[:description] = message.split(/\n+/, 2)
|
162
|
+
end
|
163
|
+
if assigns[:title] && m
|
164
|
+
t_match = assigns[:title].strip == m['title'].strip
|
165
|
+
if assigns[:description]
|
166
|
+
b_match = assigns[:description].strip == m['description'].strip
|
167
|
+
end
|
168
|
+
if t_match && b_match
|
169
|
+
e.unlink if e
|
170
|
+
abort 'No change.' if assigns.dup.delete_if { |k, v|
|
171
|
+
[:title, :description].include? k
|
172
|
+
}
|
173
|
+
end
|
174
|
+
end
|
175
|
+
m = throb { api.patch uri, assigns }.body
|
176
|
+
puts format_milestone(m)
|
177
|
+
puts 'Updated.'
|
178
|
+
e.unlink if e
|
179
|
+
end
|
124
180
|
when 'destroy'
|
181
|
+
require_milestone
|
125
182
|
throb { api.delete uri }
|
126
183
|
puts 'Milestone deleted.'
|
127
184
|
end
|
data/lib/ghi/commands/open.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
module GHI
|
2
2
|
module Commands
|
3
3
|
class Open < Command
|
4
|
+
attr_accessor :editor
|
5
|
+
attr_accessor :web
|
6
|
+
|
4
7
|
def options
|
5
|
-
#--
|
6
|
-
# TODO: Support shortcuts, e.g,
|
7
|
-
#
|
8
|
-
# ghi open "Issue Title"
|
9
|
-
#++
|
10
8
|
OptionParser.new do |opts|
|
11
9
|
opts.banner = <<EOF
|
12
10
|
usage: ghi open [options]
|
@@ -16,16 +14,24 @@ EOF
|
|
16
14
|
opts.on '-l', '--list', 'list open tickets' do
|
17
15
|
self.action = 'index'
|
18
16
|
end
|
17
|
+
opts.on('-w', '--web') { self.web = true }
|
19
18
|
opts.separator ''
|
20
19
|
opts.separator 'Issue modification options'
|
21
20
|
opts.on '-m', '--message [<text>]', 'describe issue' do |text|
|
22
|
-
|
21
|
+
if text
|
22
|
+
assigns[:title], assigns[:body] = text.split(/\n+/, 2)
|
23
|
+
else
|
24
|
+
self.editor = true
|
25
|
+
end
|
23
26
|
end
|
24
27
|
opts.on(
|
25
28
|
'-u', '--[no-]assign [<user>]', 'assign to specified user'
|
26
29
|
) do |assignee|
|
27
30
|
assigns[:assignee] = assignee
|
28
31
|
end
|
32
|
+
opts.on '--claim', 'assign to yourself' do
|
33
|
+
assigns[:assignee] = Authorization.username
|
34
|
+
end
|
29
35
|
opts.on(
|
30
36
|
'-M', '--milestone <n>', 'associate with milestone'
|
31
37
|
) do |milestone|
|
@@ -34,7 +40,7 @@ EOF
|
|
34
40
|
opts.on(
|
35
41
|
'-L', '--label <labelname>...', Array, 'associate with label(s)'
|
36
42
|
) do |labels|
|
37
|
-
assigns[:labels]
|
43
|
+
(assigns[:labels] ||= []).concat labels
|
38
44
|
end
|
39
45
|
opts.separator ''
|
40
46
|
end
|
@@ -57,17 +63,36 @@ EOF
|
|
57
63
|
args.unshift assigns[:assignee] if assigns[:assignee]
|
58
64
|
args.unshift '-u'
|
59
65
|
end
|
66
|
+
args.unshift '-w' if web
|
60
67
|
List.execute args.push('--', repo)
|
61
68
|
when 'create'
|
62
|
-
if
|
63
|
-
|
64
|
-
|
65
|
-
|
69
|
+
if web
|
70
|
+
Web.new(repo).open 'issues/new'
|
71
|
+
else
|
72
|
+
unless args.empty?
|
73
|
+
assigns[:title], assigns[:body] = args.join(' '), assigns[:title]
|
74
|
+
end
|
75
|
+
assigns[:title] = args.join ' ' unless args.empty?
|
76
|
+
if assigns[:title].nil? || editor
|
77
|
+
e = Editor.new 'GHI_ISSUE'
|
78
|
+
message = e.gets format_editor(assigns)
|
79
|
+
e.unlink "There's no issue?" if message.nil? || message.empty?
|
80
|
+
assigns[:title], assigns[:body] = message.split(/\n+/, 2)
|
81
|
+
end
|
82
|
+
i = throb { api.post "/repos/#{repo}/issues", assigns }.body
|
83
|
+
e.unlink if e
|
84
|
+
puts format_issue(i)
|
85
|
+
puts 'Opened.'
|
66
86
|
end
|
67
|
-
i = throb { api.post "/repos/#{repo}/issues", assigns }.body
|
68
|
-
puts format_issue(i)
|
69
|
-
puts 'Opened.'
|
70
87
|
end
|
88
|
+
rescue Client::Error => e
|
89
|
+
raise unless error = e.errors.first
|
90
|
+
abort "%s %s %s %s." % [
|
91
|
+
error['resource'],
|
92
|
+
error['field'],
|
93
|
+
[*error['value']].join(', '),
|
94
|
+
error['code']
|
95
|
+
]
|
71
96
|
end
|
72
97
|
end
|
73
98
|
end
|