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 +67 -1
- data/lib/git_issue/base.rb +3 -4
- data/lib/git_issue/github.rb +36 -10
- data/lib/git_issue/redmine.rb +72 -1
- data/lib/git_issue/version.rb +1 -1
- metadata +4 -4
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
|
-
|
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)
|
data/lib/git_issue/base.rb
CHANGED
@@ -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
|
|
data/lib/git_issue/github.rb
CHANGED
@@ -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
|
-
|
93
|
-
|
94
|
-
|
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 '
|
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
|
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
|
data/lib/git_issue/redmine.rb
CHANGED
@@ -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]
|
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}
|
data/lib/git_issue/version.rb
CHANGED
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:
|
4
|
+
hash: 63
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
- 7
|
9
8
|
- 8
|
10
|
-
|
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-
|
18
|
+
date: 2012-03-02 00:00:00 +09:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|