git-issue 0.8.2 → 0.8.3

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/Gemfile CHANGED
@@ -3,3 +3,4 @@ source "http://rubygems.org"
3
3
  # Specify your gem's dependencies in named_let.gemspec
4
4
  gem 'activesupport'
5
5
  gem 'pit'
6
+ gem 'term-ansicolor'
@@ -21,7 +21,7 @@ now available issue-tracker system is Redmine and Github-issues.
21
21
  $ gem install jeweler
22
22
  $ rake install
23
23
 
24
- ## Configuration
24
+ ## Configuration(Redmine)
25
25
 
26
26
  set type of issue traking system(redmine or github)
27
27
 
@@ -43,6 +43,16 @@ set your account name if using github.
43
43
 
44
44
  $ git config issue.user yuroyoro
45
45
 
46
+ ## Configuration(Github Issues)
47
+
48
+ set type of issue traking system(redmine or github)
49
+
50
+ $ git config issue.type github
51
+
52
+ set user and password of github(for authentication)
53
+
54
+ $ EDITOR=vim pit set github
55
+
46
56
  ## Usage(Redmine)
47
57
 
48
58
  git issue <command> [ticket_id] [<args>]
@@ -16,6 +16,7 @@ require 'optparse'
16
16
  require 'tempfile'
17
17
  require 'active_support/all'
18
18
  require 'shellwords'
19
+ require 'term/ansicolor'
19
20
 
20
21
  module GitIssue
21
22
  class Command
@@ -39,8 +40,9 @@ module GitIssue
39
40
 
40
41
 
41
42
  def configured_value(name, trim = true)
42
- res = `git config issue.#{name}`
43
- trim ? res.strip : res
43
+ res = `git config #{name}`
44
+ res = trim ? res.strip : res
45
+ res
44
46
  end
45
47
 
46
48
  def global_configured_value(name)
@@ -129,7 +131,7 @@ module GitIssue
129
131
  status = true
130
132
 
131
133
  begin
132
- its_type = Helper.configured_value('type')
134
+ its_type = Helper.configured_value('issue.type')
133
135
 
134
136
  # Use global config for hub
135
137
  if its_type.blank?
@@ -2,6 +2,7 @@
2
2
 
3
3
  class GitIssue::Base
4
4
  include GitIssue::Helper
5
+ include Term::ANSIColor
5
6
 
6
7
  attr_reader :apikey, :command, :tickets, :options
7
8
  attr_accessor :sysout, :syserr
@@ -251,6 +252,7 @@ class GitIssue::Base
251
252
  opts.on("--remote=VALUE", 'on publish, remote repository to push branch ') {|v| @options[:remote] = v}
252
253
  opts.on("--onto=VALUE", 'on rebase, start new branch with HEAD equal to "newbase" ') {|v| @options[:onto] = v}
253
254
 
255
+ opts.on("--no-color", "turn off colored output"){@no_color = true }
254
256
  opts.on("--debug", "debug print"){@debug= true }
255
257
  }
256
258
 
@@ -264,6 +266,8 @@ class GitIssue::Base
264
266
  @syserr.puts msg
265
267
  end
266
268
 
267
-
269
+ def apply_colors(str, *colors)
270
+ @no_color.present? ? str : (colors.map(&method(:send)) + [str, reset]).join
271
+ end
268
272
  end
269
273
 
@@ -7,17 +7,28 @@ class GitIssue::Github < GitIssue::Base
7
7
  def initialize(args, options = {})
8
8
  super(args, options)
9
9
 
10
- url = `git config remote.origin.url`.strip
11
- @repo = url.match(/github.com[:\/](.+)\.git/)[1]
10
+ @repo = configured_value('issue.repo')
11
+ if @repo.blank?
12
+ url = `git config remote.origin.url`.strip
13
+ @repo = url.match(/github.com[:\/](.+)\.git/)[1]
14
+ end
12
15
 
13
- @user = options[:user] || configured_value('user')
16
+ @user = options[:user] || configured_value('issue.user')
14
17
  @user = global_configured_value('github.user') if @user.blank?
15
18
  @user = Pit.get("github", :require => {
16
19
  "user" => "Your user name in GitHub",
17
20
  })["user"] if @user.blank?
18
21
 
19
22
  configure_error('user', "git config issue.user yuroyoro") if @user.blank?
20
- @sslNoVerify = @options[:sslNoVerify] && RUBY_VERSION < '1.9.0'
23
+ @ssl_options = {}
24
+ if @options.key?(:sslNoVerify) && RUBY_VERSION < "1.9.0"
25
+ @ssl_options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE
26
+ elsif configured_value('http.sslVerify') == "false"
27
+ @ssl_options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE
28
+ end
29
+ if (ssl_cert = configured_value('http.sslCert'))
30
+ @ssl_options[:ssl_ca_cert] = ssl_cert
31
+ end
21
32
  end
22
33
 
23
34
  def commands
@@ -54,7 +65,7 @@ class GitIssue::Github < GitIssue::Base
54
65
 
55
66
  url = to_url("repos", @repo, 'issues')
56
67
 
57
- issues = fetch_json(url, params)
68
+ issues = fetch_json(url, options, params)
58
69
  issues = issues.sort_by{|i| i['number'].to_i} unless params[:sort] || params[:direction]
59
70
 
60
71
  t_max = issues.map{|i| mlength(i['title'])}.max
@@ -64,12 +75,12 @@ class GitIssue::Github < GitIssue::Base
64
75
  or_zero = lambda{|v| v.blank? ? "0" : v }
65
76
 
66
77
  issues.each do |i|
67
- puts sprintf("#%-4d %s %s %s %s c:%s v:%s p:%s %s %s",
68
- i['number'].to_i,
69
- i['state'],
78
+ puts sprintf("%s %s %s %s %s c:%s v:%s p:%s %s %s",
79
+ apply_fmt_colors(:id, sprintf('#%-4d', i['number'].to_i)),
80
+ apply_fmt_colors(:state, i['state']),
70
81
  mljust(i['title'], t_max),
71
- mljust(i['user']['login'], u_max),
72
- mljust(i['labels'].map{|l| l['name']}.join(','), l_max),
82
+ apply_fmt_colors(:login, mljust(i['user']['login'], u_max)),
83
+ apply_fmt_colors(:labels, mljust(i['labels'].map{|l| l['name']}.join(','), l_max)),
73
84
  or_zero.call(i['comments']),
74
85
  or_zero.call(i['votes']),
75
86
  or_zero.call(i['position']),
@@ -185,18 +196,9 @@ class GitIssue::Github < GitIssue::Base
185
196
  URI.join(ROOT, path_list.join("/"))
186
197
  end
187
198
 
188
- def fetch_json(url, params = {})
189
- url += "?" + params.map{|k,v| "#{k}=#{v}"}.join("&") unless params.empty?
190
-
191
- puts url if @debug
192
-
193
- password = options[:password] || get_password(@user)
194
- opt = {"Authorization" => "Basic " + Base64.encode64("#{@user}:#{password}")}
195
-
196
- opt[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE if @sslNoVerify
197
- json = open(url, opt) {|io|
198
- JSON.parse(io.read)
199
- }
199
+ def fetch_json(url, options = {}, params = {})
200
+ response = send_request(url, {},options, params, :get)
201
+ json = JSON.parse(response.body)
200
202
 
201
203
  if @debug
202
204
  puts '-' * 80
@@ -210,8 +212,8 @@ class GitIssue::Github < GitIssue::Base
210
212
 
211
213
  def fetch_issue(ticket_id, params = {})
212
214
  url = to_url("repos", @repo, 'issues', ticket_id)
213
- url += "?" + params.map{|k,v| "#{k}=#{v}"}.join("&") unless params.empty?
214
- json = fetch_json(url)
215
+ # url += "?" + params.map{|k,v| "#{k}=#{v}"}.join("&") unless params.empty?
216
+ json = fetch_json(url, {}, params)
215
217
 
216
218
  issue = json['issue'] || json
217
219
  raise "no such issue #{ticket} : #{base}" unless issue
@@ -231,7 +233,7 @@ class GitIssue::Github < GitIssue::Base
231
233
  end
232
234
 
233
235
  def post_json(url, json, options, params = {})
234
- response = send_json(url, json, options, params, :post)
236
+ response = send_request(url, json, options, params, :post)
235
237
  json = JSON.parse(response.body)
236
238
 
237
239
  raise error_message(json) unless response_success?(response)
@@ -239,7 +241,7 @@ class GitIssue::Github < GitIssue::Base
239
241
  end
240
242
 
241
243
  def put_json(url, json, options, params = {})
242
- response = send_json(url, json, options, params, :put)
244
+ response = send_request(url, json, options, params, :put)
243
245
  json = JSON.parse(response.body)
244
246
 
245
247
  raise error_message(json) unless response_success?(response)
@@ -252,7 +254,7 @@ class GitIssue::Github < GitIssue::Base
252
254
  msg.join("\n ")
253
255
  end
254
256
 
255
- def send_json(url, json, options, params = {}, method = :post)
257
+ def send_request(url, json = {}, options = {}, params = {}, method = :post)
256
258
  url = "#{url}"
257
259
  uri = URI.parse(url)
258
260
 
@@ -265,8 +267,23 @@ class GitIssue::Github < GitIssue::Base
265
267
 
266
268
  https = Net::HTTP.new(uri.host, uri.port)
267
269
  https.use_ssl = true
268
- https.verify_mode = OpenSSL::SSL::VERIFY_NONE
270
+ https.verify_mode = @ssl_options[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_NONE
271
+
272
+ store = OpenSSL::X509::Store.new
273
+ if @ssl_options[:ssl_ca_cert].present?
274
+ if File.directory? @ssl_options[:ssl_ca_cert]
275
+ store.add_path @ssl_options[:ssl_ca_cert]
276
+ else
277
+ store.add_file @ssl_options[:ssl_ca_cert]
278
+ end
279
+ http.cert_store = store
280
+ else
281
+ store.set_default_paths
282
+ end
283
+ https.cert_store = store
284
+
269
285
  https.set_debug_output $stderr if @debug && https.respond_to?(:set_debug_output)
286
+
270
287
  https.start{|http|
271
288
 
272
289
  path = "#{uri.path}"
@@ -275,6 +292,7 @@ class GitIssue::Github < GitIssue::Base
275
292
  request = case method
276
293
  when :post then Net::HTTP::Post.new(path)
277
294
  when :put then Net::HTTP::Put.new(path)
295
+ when :get then Net::HTTP::Get.new(path)
278
296
  else raise "unknown method #{method}"
279
297
  end
280
298
 
@@ -287,7 +305,7 @@ class GitIssue::Github < GitIssue::Base
287
305
  request.basic_auth @user, password
288
306
 
289
307
  request.set_content_type("application/json")
290
- request.body = json.to_json
308
+ request.body = json.to_json if json.present?
291
309
 
292
310
  response = http.request(request)
293
311
  if @debug
@@ -331,7 +349,7 @@ class GitIssue::Github < GitIssue::Base
331
349
  end
332
350
  end
333
351
 
334
- msg << sprintf("%s : %s", mljust('labels', 18), issue['labels'].map{|l| l['name']}.join(","))
352
+ msg << sprintf("%s : %s", mljust('labels', 18), apply_fmt_colors(:labels, issue['labels'].map{|l| l['name']}.join(",")))
335
353
  msg << sprintf("%s : %s", mljust('html_url', 18), issue['html_url'])
336
354
  msg << sprintf("%s : %s", mljust('updated_at', 18), Time.parse(issue['updated_at']))
337
355
 
@@ -352,14 +370,14 @@ class GitIssue::Github < GitIssue::Base
352
370
  end
353
371
 
354
372
  def issue_title(issue)
355
- "[#{issue['state']}] ##{issue['number']} #{issue['title']}"
373
+ "[#{apply_fmt_colors(:state, issue['state'])}] #{apply_fmt_colors(:id, "##{issue['number']}")} #{issue['title']}"
356
374
  end
357
375
 
358
376
  def issue_author(issue)
359
377
  author = issue['user']['login']
360
378
  created_at = issue['created_at']
361
379
 
362
- msg = "#{author} opened this issue #{Time.parse(created_at)}"
380
+ msg = "#{apply_fmt_colors(:login, author)} opened this issue #{Time.parse(created_at)}"
363
381
  msg
364
382
  end
365
383
 
@@ -399,5 +417,14 @@ class GitIssue::Github < GitIssue::Base
399
417
  opts
400
418
  end
401
419
 
420
+ def apply_fmt_colors(key, str)
421
+ fmt_colors[key.to_sym] ? apply_colors(str, *Array(fmt_colors[key.to_sym])) : str
422
+ end
423
+
424
+ def fmt_colors
425
+ @fmt_colors ||= { :id => [:bold, :cyan], :state => :blue,
426
+ :login => :magenta, :labels => :yellow}
427
+ end
428
+
402
429
  end
403
430
  end
@@ -6,15 +6,15 @@ class Redmine < GitIssue::Base
6
6
  def initialize(args, options = {})
7
7
  super(args, options)
8
8
 
9
- @apikey = options[:apikey] || configured_value('apikey')
9
+ @apikey = options[:apikey] || configured_value('issue.apikey')
10
10
  configure_error('apikey', "git config issue.apikey some_api_key") if @apikey.blank?
11
11
 
12
- @url = options[:url] || configured_value('url')
12
+ @url = options[:url] || configured_value('issue.url')
13
13
  configure_error('url', "git config issue.url http://example.com/redmine") if @url.blank?
14
14
  end
15
15
 
16
16
  def default_cmd
17
- Helper.configured_value('project').blank? ? :list : :project
17
+ Helper.configured_value('issue.project').blank? ? :list : :project
18
18
  end
19
19
 
20
20
  def commands
@@ -71,7 +71,7 @@ class Redmine < GitIssue::Base
71
71
  def add(options = {})
72
72
  property_names = [:project_id, :subject, :description, :done_ratio, :status_id, :priority_id, :tracker_id, :assigned_to_id, :category_id, :fixed_version_id, :notes]
73
73
 
74
- project_id = options[:project_id] || Helper.configured_value('project')
74
+ project_id = options[:project_id] || Helper.configured_value('issue.project')
75
75
  if options.slice(*property_names).empty?
76
76
  issue = read_issue_from_editor({"project" => {"id" => project_id}}, options)
77
77
  description = issue.delete(:notes)
@@ -85,7 +85,7 @@ class Redmine < GitIssue::Base
85
85
  end
86
86
 
87
87
  json = build_issue_json(options, property_names)
88
- json["issue"][:project_id] ||= Helper.configured_value('project')
88
+ json["issue"][:project_id] ||= Helper.configured_value('issue.project')
89
89
 
90
90
  url = to_url('issues')
91
91
 
@@ -144,7 +144,7 @@ class Redmine < GitIssue::Base
144
144
  end
145
145
 
146
146
  def project(options = {})
147
- project_id = Helper.configured_value('project')
147
+ project_id = Helper.configured_value('issue.project')
148
148
  project_id = options[:ticket_id] if project_id.blank?
149
149
  raise 'project_id is required.' unless project_id
150
150
  list(options.merge(:query => "project_id=#{project_id}"))
@@ -204,6 +204,10 @@ class Redmine < GitIssue::Base
204
204
  end
205
205
 
206
206
  http = Net::HTTP.new(uri.host, uri.port)
207
+ if uri.scheme == 'https'
208
+ http.use_ssl = true
209
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
210
+ end
207
211
  http.set_debug_output $stderr if @debug && http.respond_to?(:set_debug_output)
208
212
  http.start{|http|
209
213
 
@@ -238,7 +242,7 @@ class Redmine < GitIssue::Base
238
242
 
239
243
 
240
244
  def issue_title(issue)
241
- "[#{issue['project']['name']}] #{issue['tracker']['name']} ##{issue['id']} #{issue['subject']}"
245
+ "[#{apply_colors(issue['project']['name'], :green)}] #{apply_colors(issue['tracker']['name'], :yellow)} #{apply_fmt_colors(:id, "##{issue['id']}")} #{issue['subject']}"
242
246
  end
243
247
 
244
248
  def issue_author(issue)
@@ -246,7 +250,7 @@ class Redmine < GitIssue::Base
246
250
  created_on = issue['created_on']
247
251
  updated_on = issue['updated_on']
248
252
 
249
- msg = "#{author}が#{time_ago_in_words(created_on)}に追加"
253
+ msg = "#{apply_fmt_colors(:assigned_to, author)}が#{time_ago_in_words(created_on)}に追加"
250
254
  msg += ", #{time_ago_in_words(updated_on)}に更新" unless created_on == updated_on
251
255
  msg
252
256
  end
@@ -276,13 +280,13 @@ class Redmine < GitIssue::Base
276
280
  add_prop = Proc.new{|name|
277
281
  title = property_title(name)
278
282
  value = issue[name] || ""
279
- props << [title, value]
283
+ props << [title, value, name]
280
284
  }
281
285
  add_prop_name = Proc.new{|name|
282
286
  title = property_title(name)
283
287
  value = ''
284
288
  value = prop_name.call(name)
285
- props << [title, value]
289
+ props << [title, value, name]
286
290
  }
287
291
 
288
292
  add_prop_name.call('status')
@@ -297,12 +301,13 @@ class Redmine < GitIssue::Base
297
301
  # acd custom_fields if it have value.
298
302
  if custom_fields = issue[:custom_fields] && custom_fields.reject{|cf| cf['value'].nil? || cf['value'].empty? }
299
303
  custom_fields.each do |cf|
300
- props << [cf['name'], cf['value']]
304
+ props << [cf['name'], cf['value'], cf['name']]
301
305
  end
302
306
  end
303
307
 
304
308
  props.each_with_index do |p,n|
305
- row = sprintf("%s : %s", mljust(p.first, 18), mljust(p.last.to_s, 24))
309
+ title, value, name = p
310
+ row = sprintf("%s : %s", mljust(title, 18), apply_fmt_colors(name, mljust(value.to_s, 24)))
306
311
  if n % 2 == 0
307
312
  msg << row
308
313
  else
@@ -367,7 +372,7 @@ class Redmine < GitIssue::Base
367
372
  def format_jounal(j, n)
368
373
  jnl = []
369
374
 
370
- jnl << "##{n + 1} - #{j['user']['name']}が#{time_ago_in_words(j['created_on'])}に更新"
375
+ jnl << "##{n + 1} - #{apply_fmt_colors(:assigned_to, j['user']['name'])}が#{time_ago_in_words(j['created_on'])}に更新"
371
376
  jnl << "-" * 78
372
377
  j['details'].each do |d|
373
378
  log = "#{property_title(d['name'])}を"
@@ -385,7 +390,7 @@ class Redmine < GitIssue::Base
385
390
  def format_changesets(changesets)
386
391
  cs = []
387
392
  changesets.sort_by{|c| c['committed_on'] }.each do |c|
388
- cs << "リビジョン: #{c['revision'][0..10]} #{c['user']['name']}が#{time_ago_in_words(c['committed_on'])}に追加"
393
+ cs << "リビジョン: #{apply_colors(c['revision'][0..10], :cyan)} #{apply_fmt_colors(:assigned_to, c['user']['name'])}が#{time_ago_in_words(c['committed_on'])}に追加"
389
394
  cs += c['comments'].split("\n").to_a
390
395
  cs << ""
391
396
  end
@@ -395,10 +400,12 @@ class Redmine < GitIssue::Base
395
400
  def format_relations(relations)
396
401
  relations.map{|r|
397
402
  issue = fetch_issue(r['issue_id'])
398
- "#{relations_label(r['relation_type'])} #{issue_title(issue)} #{issue['status']['name']} #{issue['start_date']} "
403
+ "#{relations_label(r['relation_type'])} #{issue_title(issue)} #{apply_fmt_colors(:status, issue['status']['name'])} #{issue['start_date']} "
399
404
  }
400
405
  end
401
406
 
407
+ DEFAULT_FORMAT = "%I %S | %A | %s %T %P | %V %C |"
408
+
402
409
  def format_issue_tables(issues_json)
403
410
  name_of = lambda{|issue, name| issue[name]['name'] rescue ""}
404
411
 
@@ -429,7 +436,9 @@ class Redmine < GitIssue::Base
429
436
  :subject => 80
430
437
  }
431
438
 
432
- fmt = configured_value('defaultformat', false)
439
+ fmt = configured_value('issue.defaultformat', false)
440
+ fmt = DEFAULT_FORMAT unless fmt.present?
441
+
433
442
  fmt_chars = { :I => :id, :S => :subject,
434
443
  :A => :assigned_to, :s => :status, :T => :tracker,
435
444
  :P => :priority, :p => :project, :V => :version,
@@ -440,7 +449,9 @@ class Redmine < GitIssue::Base
440
449
  fmt_chars.each do |k, v|
441
450
  res.gsub!(/\%(\d*)#{k}/) do |s|
442
451
  max = $1.blank? ? max_length[v] : $1.to_i
443
- max ? mljust(i[v], max) : i[v]
452
+ str = max ? mljust(i[v], max) : i[v]
453
+ colored = fmt_colors[v] ? apply_fmt_colors(v, str) : str
454
+ colored
444
455
  end
445
456
  end
446
457
  res
@@ -449,6 +460,16 @@ class Redmine < GitIssue::Base
449
460
  issues.map{|i| format_to.call(i) }
450
461
  end
451
462
 
463
+ def apply_fmt_colors(key, str)
464
+ fmt_colors[key.to_sym] ? apply_colors(str, *Array(fmt_colors[key.to_sym])) : str
465
+ end
466
+
467
+ def fmt_colors
468
+ @fmt_colors ||= { :id => [:bold, :cyan], :status => :blue,
469
+ :priority => :green, :assigned_to => :magenta,
470
+ :tracker => :yellow}
471
+ end
472
+
452
473
  def output_issues(issues)
453
474
  if options[:raw_id]
454
475
  issues.each do |i|
@@ -482,7 +503,7 @@ class Redmine < GitIssue::Base
482
503
  def read_issue_from_editor(issue, options = {})
483
504
  id_of = lambda{|name| issue[name] ? sprintf('%2s : %s', issue[name]["id"] , issue[name]['name'] ): ""}
484
505
 
485
- memofile = configured_value('memofile')
506
+ memofile = configured_value('issue.memofile')
486
507
  memo = File.open(memofile).read.lines.map{|l| "# #{l}"}.join("") unless memofile.blank?
487
508
 
488
509
  message = <<-MSG
@@ -1,3 +1,3 @@
1
1
  module GitIssue
2
- VERSION = "0.8.2"
2
+ VERSION = "0.8.3"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-issue
3
3
  version: !ruby/object:Gem::Version
4
- hash: 59
4
+ hash: 57
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 8
9
- - 2
10
- version: 0.8.2
9
+ - 3
10
+ version: 0.8.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tomohito Ozaki
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-04-19 00:00:00 +09:00
18
+ date: 2012-06-25 00:00:00 +09:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency