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 +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
|