git-issue 0.7.8 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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