git-issue 0.7.8 → 0.8.0

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/lib/git_issue.rb CHANGED
@@ -15,6 +15,7 @@ require 'json'
15
15
  require 'optparse'
16
16
  require 'tempfile'
17
17
  require 'active_support/all'
18
+ require 'shellwords'
18
19
 
19
20
  module GitIssue
20
21
  class Command
@@ -56,7 +57,72 @@ module GitIssue
56
57
  end
57
58
  end
58
59
 
59
- module_function :configured_value, :global_configured_value, :configure_error, :its_klass_of
60
+ def git_editor
61
+ # possible: ~/bin/vi, $SOME_ENVIRONMENT_VARIABLE, "C:\Program Files\Vim\gvim.exe" --nofork
62
+ editor = `git var GIT_EDITOR`
63
+ editor = ENV[$1] if editor =~ /^\$(\w+)$/
64
+ editor = File.expand_path editor if (editor =~ /^[~.]/ or editor.index('/')) and editor !~ /["']/
65
+ editor.shellsplit
66
+ end
67
+
68
+ def git_dir
69
+ `git rev-parse -q --git-dir`.strip
70
+ end
71
+
72
+ def split_head_and_body(text)
73
+ title, body = '', ''
74
+ text.each_line do |line|
75
+ next if line.index('#') == 0
76
+ ((body.empty? and line =~ /\S/) ? title : body) << line
77
+ end
78
+ title.tr!("\n", ' ')
79
+ title.strip!
80
+ body.strip!
81
+
82
+ [title =~ /\S/ ? title : nil, body =~ /\S/ ? body : nil]
83
+ end
84
+
85
+ def read_body(file)
86
+ f = open(file)
87
+ body = f.read
88
+ f.close
89
+ body.strip
90
+ end
91
+
92
+ def get_title_and_body_from_editor(message=nil)
93
+ open_editor(message) do |text|
94
+ title, body = split_head_and_body(text)
95
+ abort "Aborting due to empty issue title" unless title
96
+ [title, body]
97
+ end
98
+ end
99
+
100
+ def get_body_from_editor(message=nil)
101
+ open_editor(message) do |text|
102
+ abort "Aborting due to empty message" if text.empty?
103
+ text
104
+ end
105
+ end
106
+
107
+ def open_editor(message = nil, abort_if_not_modified = true , &block)
108
+ message_file = File.join(git_dir, 'ISSUE_MESSAGE')
109
+ File.open(message_file, 'w') { |msg|
110
+ msg.puts message
111
+ }
112
+ edit_cmd = Array(git_editor).dup
113
+ edit_cmd << '-c' << 'set ft=gitcommit' if edit_cmd[0] =~ /^[mg]?vim$/
114
+ edit_cmd << message_file
115
+
116
+ system(*edit_cmd)
117
+ abort "can't open text editor for issue message" unless $?.success?
118
+
119
+ text = read_body(message_file)
120
+ abort "Aborting cause messages didn't modified." if message == text && abort_if_not_modified
121
+
122
+ yield text
123
+ end
124
+
125
+ module_function :configured_value, :global_configured_value, :configure_error, :its_klass_of, :get_title_and_body_from_editor, :get_body_from_editor
60
126
  end
61
127
 
62
128
  def self.main(argv)
@@ -179,13 +179,11 @@ class GitIssue::Base
179
179
  s.split(//u).each do |c|
180
180
  next if cnt > n
181
181
  chars << c
182
- cnt += 1
183
- cnt += 1 if c.length > 1
182
+ cnt += c =~ /^[^ -~。-゚]*$/ ? 2 : 1
184
183
  end
185
184
  if cnt > n
186
185
  chars.pop
187
- cnt -= 1
188
- cnt -= 1 if chars.last.length > 1
186
+ cnt -= chars.last =~ /^[^ -~。-゚]*$/ ? 2 : 1
189
187
  end
190
188
  chars << " " * (n - cnt) if n > cnt
191
189
  chars.join
@@ -266,5 +264,6 @@ class GitIssue::Base
266
264
  @syserr.puts msg
267
265
  end
268
266
 
267
+
269
268
  end
270
269
 
@@ -7,10 +7,6 @@ class GitIssue::Github < GitIssue::Base
7
7
  def initialize(args, options = {})
8
8
  super(args, options)
9
9
 
10
- @apikey = options[:apikey] || configured_value('apikey')
11
- @apikey = global_configured_value('github.token') if @apikey.blank?
12
- configure_error('apikey', "git config issue.apikey some_api_key") if @apikey.blank?
13
-
14
10
  url = `git config remote.origin.url`.strip
15
11
  @repo = url.match(/github.com[:\/](.+)\.git/)[1]
16
12
 
@@ -27,6 +23,7 @@ class GitIssue::Github < GitIssue::Base
27
23
  def commands
28
24
  cl = super
29
25
  cl << GitIssue::Command.new(:mention, :men, 'create a comment to given issue')
26
+ cl << GitIssue::Command.new(:close , :cl, 'close an issue with comment. comment is optional.')
30
27
  end
31
28
 
32
29
  def show(options = {})
@@ -89,9 +86,14 @@ class GitIssue::Github < GitIssue::Base
89
86
  def add(options = {})
90
87
  property_names = [:title, :body, :assignee, :milestone, :labels]
91
88
 
92
- required_properties = [:title]
93
- required_properties.each do |name|
94
- options[name] = prompt(name) unless options[name]
89
+ message = <<-MSG
90
+ ### Write title here ###
91
+
92
+ ### descriptions here ###
93
+ MSG
94
+
95
+ unless options[:title]
96
+ options[:title], options[:body] = get_title_and_body_from_editor(message)
95
97
  end
96
98
 
97
99
  json = build_issue_json(options, property_names)
@@ -107,6 +109,12 @@ class GitIssue::Github < GitIssue::Base
107
109
 
108
110
  property_names = [:title, :body, :assignee, :milestone, :labels, :state]
109
111
 
112
+ if options.slice(*property_names).empty?
113
+ issue = fetch_issue(ticket)
114
+ message = "#{issue['title']}\n\n#{issue['body']}"
115
+ options[:title], options[:body] = get_title_and_body_from_editor(message)
116
+ end
117
+
110
118
  json = build_issue_json(options, property_names)
111
119
  url = to_url("repos", @repo, 'issues', ticket)
112
120
 
@@ -119,8 +127,8 @@ class GitIssue::Github < GitIssue::Base
119
127
  ticket = options[:ticket_id]
120
128
  raise 'ticket_id is required.' unless ticket
121
129
 
122
- body = options[:body]
123
- raise 'commnet body is required.' unless body
130
+ body = options[:body] || get_body_from_editor("### comment here ###")
131
+ raise 'comment body is required.' if body.empty?
124
132
 
125
133
  json = { :body => body }
126
134
  url = to_url("repos", @repo, 'issues', ticket, 'comments')
@@ -151,6 +159,24 @@ class GitIssue::Github < GitIssue::Base
151
159
  show(options)
152
160
  end
153
161
 
162
+ def close(options = {})
163
+ ticket = options[:ticket_id]
164
+ raise 'ticket_id is required.' unless ticket
165
+
166
+ body = options[:body] || get_body_from_editor("### comment here ###")
167
+
168
+ json = {:state => 'closed' }
169
+ url = to_url("repos", @repo, 'issues', ticket)
170
+
171
+ issue = post_json(url, json, options)
172
+
173
+ comment_json = { :body => body }
174
+ comment_url = to_url("repos", @repo, 'issues', ticket, 'comments')
175
+ post_json(comment_url, comment_json, options)
176
+
177
+ puts "closed issue #{oneline_issue(issue)}"
178
+ end
179
+
154
180
  private
155
181
 
156
182
  ROOT = 'https://api.github.com/'
@@ -367,7 +393,7 @@ class GitIssue::Github < GitIssue::Base
367
393
  opts.on("--direction=VALUE", "Query of listing issues, (asc or desc, default: desc.)"){|v| @options[:direction] = v }
368
394
  opts.on("--since=VALUE", "Query of listing issue, (Optional string of a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ)"){|v| @options[:since] = v }
369
395
 
370
- opts.on("--password=VALUE", "For Authorizaion of create/update issue. Github API v3 does'nt supports API token base authorization for now. then, use Basic Authorizaion instead token." ){|v| @options[:password]}
396
+ opts.on("--password=VALUE", "For Authorizaion of create/update issue. Github API v3 doesn't supports API token base authorization for now. then, use Basic Authorizaion instead token." ){|v| @options[:password]}
371
397
  opts.on("--sslnoverify", "don't verify SSL"){|v| @options[:sslNoVerify] = true}
372
398
  opts
373
399
  end
@@ -71,6 +71,14 @@ 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')
75
+ if options.slice(*property_names).empty?
76
+ issue = read_issue_from_editor({"project" => {"id" => project_id}}, options)
77
+ description = issue.delete(:notes)
78
+ issue[:description] = description
79
+ options.merge!(issue)
80
+ end
81
+
74
82
  required_properties = [:subject, :description]
75
83
  required_properties.each do |name|
76
84
  options[name] = prompt(name) unless options[name]
@@ -91,6 +99,13 @@ class Redmine < GitIssue::Base
91
99
 
92
100
  property_names = [:subject, :done_ratio, :status_id, :priority_id, :tracker_id, :assigned_to_id, :category_id, :fixed_version_id, :notes]
93
101
 
102
+ if options.slice(*property_names).empty?
103
+ org_issue = fetch_issue(ticket, options)
104
+ update_attrs = read_issue_from_editor(org_issue, options)
105
+ update_attrs = update_attrs.reject{|k,v| v.present? && org_issue[k] == v}
106
+ options.merge!(update_attrs)
107
+ end
108
+
94
109
  json = build_issue_json(options, property_names)
95
110
 
96
111
  url = to_url('issues', ticket)
@@ -421,7 +436,7 @@ class Redmine < GitIssue::Base
421
436
  end
422
437
 
423
438
  def build_issue_json(options, property_names)
424
- json = {"issue" => property_names.inject({}){|h,k| h[k] = options[k] if options[k]; h} }
439
+ json = {"issue" => property_names.inject({}){|h,k| h[k] = options[k] if options[k].present?; h} }
425
440
 
426
441
  if custom_fields = options[:custom_fields]
427
442
  json['custom_fields'] = custom_fields.split(",").map{|s| k,*v = s.split(":");{'id' => k.to_i, 'value' => v.join }}
@@ -429,6 +444,62 @@ class Redmine < GitIssue::Base
429
444
  json
430
445
  end
431
446
 
447
+ def read_issue_from_editor(issue, options = {})
448
+ id_of = lambda{|name| issue[name] ? issue[name]["id"] : ""}
449
+
450
+ message = <<-MSG
451
+ #{issue["subject"].present? ? issue["subject"].chomp : "### subject here ###"}
452
+
453
+ Project : #{id_of.call("project")}
454
+ Tracker : #{id_of.call("tracker")}
455
+ Status : #{id_of.call("status")}
456
+ Priority : #{id_of.call("priority")}
457
+ Category : #{id_of.call("category")}
458
+ Assigned : #{id_of.call("assigned_to")}
459
+ Version : #{id_of.call("fixed_version")}
460
+
461
+ ### notes here ###
462
+ MSG
463
+ body = get_body_from_editor(message)
464
+
465
+ subject, dummy, project_id, tracker_id, status_id, priority_id, category_id, assigned_to_id, fixed_version_id, dummy, *notes = body.lines.to_a
466
+
467
+ notes = if notes.present?
468
+ notes.reject{|line| line.chomp == "### notes here ###"}.join("")
469
+ else
470
+ nil
471
+ end
472
+
473
+ if @debug
474
+ puts "------"
475
+ puts "sub: #{subject}"
476
+ puts "pid: #{project_id}"
477
+ puts "tid: #{tracker_id}"
478
+ puts "sid: #{status_id}"
479
+ puts "prd: #{priority_id}"
480
+ puts "cat: #{category_id}"
481
+ puts "ass: #{assigned_to_id}"
482
+ puts "vss: #{fixed_version_id}"
483
+ puts "nos: #{notes}"
484
+ puts "------"
485
+ end
486
+
487
+ take_id = lambda{|s|
488
+ x, i = s.chomp.split(":")
489
+ i.present? ? i.strip.to_i : nil
490
+ }
491
+
492
+ { :subject => subject.chomp, :project_id => take_id.call(project_id),
493
+ :tracker_id => take_id.call(tracker_id),
494
+ :status_id => take_id.call(status_id),
495
+ :priority_id => take_id.call(priority_id),
496
+ :category_id => take_id.call(category_id),
497
+ :assigned_to_id => take_id.call(assigned_to_id),
498
+ :fixed_version_id => take_id.call(fixed_version_id),
499
+ :notes => notes
500
+ }
501
+ end
502
+
432
503
  def opt_parser
433
504
  opts = super
434
505
  opts.on("--supperss_journals", "-j", "do not show issue journals"){|v| @options[:supperss_journals] = true}
@@ -1,3 +1,3 @@
1
1
  module GitIssue
2
- VERSION = "0.7.8"
2
+ VERSION = "0.8.0"
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: 19
4
+ hash: 63
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 7
9
8
  - 8
10
- version: 0.7.8
9
+ - 0
10
+ version: 0.8.0
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-03-01 00:00:00 +09:00
18
+ date: 2012-03-02 00:00:00 +09:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency