ghi 0.9.0.dev1 → 0.9.0.20120627

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  module GHI
2
2
  module Commands
3
3
  class Edit < Command
4
- attr_accessor :edit
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.edit = true if text.nil?
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] = 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
- require_issue
64
+ self.action = 'edit'
45
65
  require_repo
66
+ require_issue
46
67
  options.parse! args
47
- if edit || assigns.empty?
48
- i = throb { api.get "/repos/#{repo}/issues/#{issue}" }.body
49
- message = Editor.gets format_editor(i)
50
- abort "There's no issue." if message.nil? || message.empty?
51
- assigns[:title], assigns[:body] = message.split(/\n+/, 2)
52
- end
53
- if assigns[:title]
54
- titles_match = assigns[:title].strip == i['title'].strip
55
- if assigns[:body]
56
- bodies_match = assigns[:body].to_s.strip == i['body'].to_s.strip
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
@@ -76,7 +76,7 @@ EOF
76
76
  if issue
77
77
  uri = "/repos/#{repo}/issues/#{issue}/labels"
78
78
  else
79
- uri = "/repos/#{repo}/labels"
79
+ uri = "/repos/#{repo}/labels"
80
80
  end
81
81
  labels = throb { api.get uri }.body
82
82
  if labels.empty?
@@ -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] = labels.join ','
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 '-v', '--verbose' do
49
- self.verbose = true
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
- unless quiet
98
- print header = format_issues_header
99
- print "\n" unless STDOUT.tty?
100
- end
101
- res = throb(
102
- 0, format_state(assigns[:state], quiet ? CURSOR[:up][1] : '#')
103
- ) { api.get uri, assigns }
104
- page header && "\r#{CURSOR[:up][1]}#{header}" do
105
- issues = res.body
106
- if verbose
107
- puts issues.map { |i| format_issue i }
108
- else
109
- puts format_issues(issues, repo.nil?)
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}/issues" : '/issues'
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
- state = assigns[:state] || 'open'
100
- print format_state state, "# #{repo} #{state} milestones"
101
- print "\n" unless STDOUT.tty?
102
- res = throb(0, format_state(state, '#')) { api.get uri, assigns }
103
- page do
104
- milestones = res.body
105
- if verbose
106
- puts milestones.map { |m| format_milestone m }
107
- else
108
- puts format_milestones(milestones)
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
- m = throb { api.get uri }.body
115
- print format_milestone(m)
116
- puts 'Issues:'
117
- List.execute %W(-q -M #{milestone} -- #{repo})
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
- m = throb { api.post uri, assigns }.body
120
- puts 'Milestone #%d created.' % m['number']
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
- throb { api.patch uri, assigns }
123
- puts 'Milestone updated.'
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
@@ -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
- assigns[:title], assigns[:body] = text.split(/\n+/, 2)
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] = 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 assigns[:title].nil?
63
- message = Editor.gets format_editor
64
- abort "There's no issue." if message.nil? || message.empty?
65
- assigns[:title], assigns[:body] = message.split(/\n+/, 2)
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