git-issue 0.7.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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Tomohito Ozaki
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,115 @@
1
+ git-issue
2
+ ====================================================
3
+
4
+ git subcommand of browse/modify issue traker's tickets.
5
+
6
+ now supporse Redmine,Github-issues
7
+
8
+ ## ScreenShots
9
+
10
+ <img src='https://github.com/yuroyoro/git-issue/raw/master/images/git-issue_screenshot-1.png' width='600'/>
11
+ <img src='https://github.com/yuroyoro/git-issue/raw/master/images/git-issue_screenshot-2.png' width='600'/>
12
+
13
+ ## Instration
14
+
15
+ $ git clone https://github.com/yuroyoro/git-issue.git
16
+ $ cd git-issue
17
+ $ rake install jeweler
18
+ $ rake build
19
+ $ gem install pkg/git-issue-<version>.gem
20
+
21
+ ## Configuration
22
+
23
+ set type of issue traking system(redmine or github)
24
+
25
+ $ git config issue.type redmine
26
+
27
+ set url of issue traking system's api endopoint.
28
+
29
+ $ git config issue.url http://redmine.example.com
30
+
31
+ set api-key for accessing issue traking system.
32
+
33
+ $ git config issue.apikey FWeaj3I9laei03A....
34
+
35
+ set repository name if using github.
36
+
37
+ $ git config issue.repo gitterb
38
+
39
+ set your account name if using github.
40
+
41
+ $ git config issue.user yuroyoro
42
+
43
+ ## Usage(Redmine)
44
+
45
+ git issue <command> [ticket_id] [<args>]
46
+
47
+ Commnads:
48
+ show s show given issue summary. if given no id, geuss id from current branch name.
49
+ list l listing issues.
50
+ mine m display issues that assigned to you.
51
+ commit c commit with filling issue subject to messsage.if given no id, geuss id from current branch name.
52
+ update u update issue properties. if given no id, geuss id from current branch name.
53
+ branch b checout to branch using specified issue id. if branch dose'nt exisits, create it. (ex ticket/id/<issue_id>)
54
+ publish pub push branch to remote repository and set upstream
55
+ rebase rb rebase branch onto specific newbase
56
+ help h show usage.
57
+ local loc listing local branches tickets
58
+ project prj listing ticket belongs to sspecified project
59
+
60
+ Options:
61
+ -a, --all update all paths in the index file
62
+ -f, --force force create branch
63
+ -v, --verbose show issue details
64
+ -n, --max-count=VALUE maximum number of issues
65
+ --oneline display short info
66
+ --raw-id output ticket number only
67
+ --remote=VALUE on publish, remote repository to push branch
68
+ --onto=VALUE on rebase, start new branch with HEAD equal to "newbase"
69
+ --debug debug print
70
+ -j, --supperss_journals show issue journals
71
+ -r, --supperss_relations show issue relations tickets
72
+ -c, --supperss_changesets show issue changesets
73
+ -q, --query=VALUE filter query of listing tickets
74
+ --subject=VALUE use the given value to update subject
75
+ --ratio=VALUE use the given value to update done-ratio(%)
76
+ --status=VALUE use the given value to update issue statues id
77
+ --priority=VALUE use the given value to update issue priority id
78
+ --tracker=VALUE use the given value to update tracker id
79
+ --assigned_to_id=VALUE use the given value to update assigned_to id
80
+ --category=VALUE use the given value to update category id
81
+ --fixed_version=VALUE use the given value to update fixed_version id
82
+ --custom_fields=VALUE value should be specifies '<custom_fields_id1>:<value2>,<custom_fields_id2>:<value2>, ...'
83
+ --notes=VALUE add notes to issue
84
+
85
+ ## Usage(Redmine)
86
+
87
+ git issue <command> [ticket_id] [<args>]
88
+
89
+ Commnads:
90
+ show s show given issue summary. if given no id, geuss id from current branch name.
91
+ list l listing issues.
92
+ mine m display issues that assigned to you.
93
+ commit c commit with filling issue subject to messsage.if given no id, geuss id from current branch name.
94
+ update u update issue properties. if given no id, geuss id from current branch name.
95
+ branch b checout to branch using specified issue id. if branch dose'nt exisits, create it. (ex ticket/id/<issue_id>)
96
+ publish pub push branch to remote repository and set upstream
97
+ rebase rb rebase branch onto specific newbase
98
+ help h show usage.
99
+
100
+ Options:
101
+ -a, --all update all paths in the index file
102
+ -f, --force force create branch
103
+ -v, --verbose show issue details
104
+ -n, --max-count=VALUE maximum number of issues
105
+ --oneline display short info
106
+ --raw-id output ticket number only
107
+ --remote=VALUE on publish, remote repository to push branch
108
+ --onto=VALUE on rebase, start new branch with HEAD equal to "newbase"
109
+ --debug debug print
110
+ -s, --supperss_commentsc show issue journals
111
+ --state=VALUE Where 'state' is either 'open' or 'closed'
112
+
113
+ ## Copyright
114
+
115
+ Copyright (c) 2011 Tomohito Ozaki. See LICENSE for details.
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "git-issue"
8
+ gem.summary = %Q{git extention command for issue tracker system.}
9
+ gem.description = %Q{git extention command for issue tracker system.}
10
+ gem.email = "ozaki@yuroyoro.com"
11
+ gem.homepage = "http://github.com/yuroyoro/git-issue"
12
+ gem.authors = ["Tomohito Ozaki"]
13
+ gem.add_development_dependency "rspec"
14
+ gem.add_development_dependency "activesupport"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'rspec/core/rake_task'
22
+ RSpec::Core::RakeTask.new(:spec) do |spec|
23
+ # spec.libs << 'lib' << 'spec'
24
+ # spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ spec.pattern = 'spec/**/*_spec.rb'
26
+ end
27
+
28
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
29
+ # spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :spec
37
+
38
+ require 'rdoc/task'
39
+ Rake::RDocTask.new do |rdoc|
40
+ if File.exist?('VERSION')
41
+ version = File.read('VERSION')
42
+ else
43
+ version = ""
44
+ end
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "git-issue #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.7.0
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
5
+ require 'git_issue'
6
+
7
+ GitIssue.main(ARGV)
8
+
@@ -0,0 +1,62 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{git-issue}
8
+ s.version = "0.7.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Tomohito Ozaki"]
12
+ s.date = %q{2012-02-23}
13
+ s.default_executable = %q{git-issue}
14
+ s.description = %q{git extention command for issue tracker system.}
15
+ s.email = %q{ozaki@yuroyoro.com}
16
+ s.executables = ["git-issue"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.markdown"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ "LICENSE",
24
+ "README.markdown",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "bin/git-issue",
28
+ "git-issue.gemspec",
29
+ "images/git-issue_screenshot-1.png",
30
+ "images/git-issue_screenshot-2.png",
31
+ "lib/git_issue.rb",
32
+ "lib/git_issue/base.rb",
33
+ "lib/git_issue/github.rb",
34
+ "lib/git_issue/redmine.rb",
35
+ "spec/git_issue/base_spec.rb",
36
+ "spec/git_issue/github_spec.rb",
37
+ "spec/git_issue/redmine_spec.rb",
38
+ "spec/git_issue_spec.rb",
39
+ "spec/spec.opts",
40
+ "spec/spec_helper.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/yuroyoro/git-issue}
43
+ s.require_paths = ["lib"]
44
+ s.rubygems_version = %q{1.5.2}
45
+ s.summary = %q{git extention command for issue tracker system.}
46
+
47
+ if s.respond_to? :specification_version then
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51
+ s.add_development_dependency(%q<rspec>, [">= 0"])
52
+ s.add_development_dependency(%q<activesupport>, [">= 0"])
53
+ else
54
+ s.add_dependency(%q<rspec>, [">= 0"])
55
+ s.add_dependency(%q<activesupport>, [">= 0"])
56
+ end
57
+ else
58
+ s.add_dependency(%q<rspec>, [">= 0"])
59
+ s.add_dependency(%q<activesupport>, [">= 0"])
60
+ end
61
+ end
62
+
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ $KCODE="UTF8" if RUBY_VERSION < '1.9.0'
5
+
6
+ require 'pp'
7
+ require 'rubygems'
8
+ require 'uri'
9
+ require 'open-uri'
10
+ require "net/http"
11
+ require "net/https"
12
+ require "uri"
13
+ require 'fileutils'
14
+ require 'json'
15
+ require 'optparse'
16
+ require 'tempfile'
17
+ require 'active_support/all'
18
+
19
+ module GitIssue
20
+ class Command
21
+ attr_reader :name, :short_name, :description
22
+ def initialize(name, short_name, description)
23
+ @name, @short_name, @description = name, short_name, description
24
+ end
25
+ end
26
+
27
+ module Helper
28
+
29
+ CONFIGURE_MESSAGE = <<-END
30
+ please set issue tracker %s.
31
+
32
+ %s
33
+ END
34
+
35
+ def configure_error(attr_name, example)
36
+ raise CONFIGURE_MESSAGE % [attr_name, example]
37
+ end
38
+
39
+
40
+ def configured_value(name)
41
+ res = `git config issue.#{name}`
42
+ res.strip
43
+ end
44
+
45
+ def global_configured_value(name)
46
+ res = `git config --global #{name}`
47
+ res.strip
48
+ end
49
+
50
+ def its_klass_of(its_type)
51
+ case its_type
52
+ when /redmine/i then GitIssue::Redmine
53
+ when /github/i then GitIssue::Github
54
+ else
55
+ raise "unknown issue tracker type : #{its_type}"
56
+ end
57
+ end
58
+
59
+ module_function :configured_value, :global_configured_value, :configure_error, :its_klass_of
60
+ end
61
+
62
+ def self.main(argv)
63
+ status = true
64
+
65
+ begin
66
+ its_type = Helper.configured_value('type')
67
+ apikey = Helper.configured_value('apikey')
68
+
69
+ # Use global config for hub
70
+ if its_type.blank?
71
+ github_user = Helper.global_configured_value('github.user')
72
+ unless github_user.blank?
73
+ its_type = 'github'
74
+ apikey = Helper.global_configured_value('github.token')
75
+ end
76
+ end
77
+
78
+ Helper.configure_error('type (redmine | github)', "git config issue.type redmine") if its_type.blank?
79
+ Helper.configure_error('apikey', "git config issue.apikey some_api_key") if apikey.blank?
80
+
81
+ its_klass = Helper.its_klass_of(its_type)
82
+ status = its_klass.new(ARGV).execute || true
83
+ rescue => e
84
+ puts e
85
+ puts e.backtrace.join("\n")
86
+ status = false
87
+ end
88
+
89
+ exit(status)
90
+ end
91
+
92
+ end
93
+
94
+ require File.dirname(__FILE__) + '/git_issue/base'
95
+ require File.dirname(__FILE__) + '/git_issue/redmine'
96
+ require File.dirname(__FILE__) + '/git_issue/github'
@@ -0,0 +1,263 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ class GitIssue::Base
4
+ include GitIssue::Helper
5
+
6
+ attr_reader :apikey, :command, :tickets, :options
7
+ attr_accessor :sysout, :syserr
8
+
9
+ def initialize(args, options = {})
10
+
11
+ @opt_parse_obj = opt_parser
12
+ args = parse_options(args)
13
+
14
+ @sysout = options[:sysout] || $stdout
15
+ @syserr = options[:syserr] || $stderr
16
+
17
+ split_ticket = lambda{|s| s.nil? || s.empty? ? nil : s.split(/,/).map{|v| v.strip.to_i} }
18
+
19
+ @tickets = []
20
+ cmd = args.shift || default_cmd
21
+
22
+ if cmd =~ /(\d+,?\s?)+/
23
+ @tickets = split_ticket.call(cmd)
24
+ cmd = :show
25
+ end
26
+ cmd = cmd.to_sym
27
+
28
+ @command = find_command(cmd)
29
+
30
+ exit_with_message("invalid command <#{cmd}>") unless @command
31
+
32
+ @tickets += args.map{|s| split_ticket.call(s)}.flatten.uniq
33
+ @tickets = [guess_ticket] if @tickets.empty?
34
+ end
35
+
36
+ def default_cmd
37
+ :list
38
+ end
39
+
40
+ def execute
41
+ if @tickets.nil? || @tickets.empty?
42
+ self.send(@command.name, @options)
43
+ else
44
+ @tickets.each do |ticket|
45
+ self.send(@command.name, @options.merge(:ticket_id => ticket))
46
+ end
47
+ end
48
+ true
49
+ end
50
+
51
+ def help(options = {})
52
+ puts @opt_parse_obj.banner
53
+ puts " Commnads:"
54
+ puts usage
55
+ puts ""
56
+ puts " Options:"
57
+ puts @opt_parse_obj.summarize
58
+ end
59
+
60
+ def publish(options = {})
61
+ ticket, branch_name = ticket_and_branch(options)
62
+ remote = options[:remote] || "origin"
63
+ system "git push -u #{remote} #{branch_name}"
64
+ end
65
+
66
+ def rebase(options = {})
67
+ raise '--onto is required.' unless options[:onto]
68
+ ticket, branch_name = ticket_and_branch(options)
69
+ onto = options[:onto]
70
+
71
+ cb = current_branch
72
+
73
+ system "git rebase --onto #{onto} #{onto} #{branch_name}"
74
+ system "git checkout #{cb}"
75
+ end
76
+
77
+ def commands
78
+ [
79
+ GitIssue::Command.new(:show, :s, 'show given issue summary. if given no id, geuss id from current branch name.'),
80
+ GitIssue::Command.new(:list, :l, 'listing issues.'),
81
+ GitIssue::Command.new(:mine, :m, 'display issues that assigned to you.'),
82
+ GitIssue::Command.new(:commit, :c, 'commit with filling issue subject to messsage.if given no id, geuss id from current branch name.'),
83
+ GitIssue::Command.new(:add, :a, 'create issue.'),
84
+ GitIssue::Command.new(:update, :u, 'update issue properties. if given no id, geuss id from current branch name.'),
85
+ GitIssue::Command.new(:branch, :b, "checout to branch using specified issue id. if branch dose'nt exisits, create it. (ex ticket/id/<issue_id>)"),
86
+
87
+ GitIssue::Command.new(:publish,:pub, "push branch to remote repository and set upstream "),
88
+ GitIssue::Command.new(:rebase, :rb, "rebase branch onto specific newbase"),
89
+
90
+ GitIssue::Command.new(:help, :h, "show usage.")
91
+ ]
92
+ end
93
+
94
+ def find_command(cmd)
95
+ cmd = cmd.to_sym
96
+ commands.find{|c| c.name == cmd || c.short_name == cmd }
97
+ end
98
+
99
+ def usage
100
+ commands.map{|c| "%-8s %s %s" % [c.name, c.short_name, c.description ] }.join("\n")
101
+ end
102
+
103
+ def time_ago_in_words(time)
104
+ t = Time.parse(time)
105
+ a = (Time.now - t).to_i
106
+
107
+ case a
108
+ when 0 then return 'just now'
109
+ when 1..59 then return a.to_s + '秒前'
110
+ when 60..119 then return '1分前'
111
+ when 120..3540 then return (a/60).to_i.to_s + '分前'
112
+ when 3541..7100 then return '1時間前'
113
+ when 7101..82800 then return ((a+99)/3600).to_i.to_s + '時間前'
114
+ when 82801..172000 then return '1日前'
115
+ when 172001..432000 then return ((a+800)/(60*60*24)).to_i.to_s + '日前'
116
+ else return ((a+800)/(60*60*24)).to_i.to_s + '日前'
117
+ end
118
+ end
119
+
120
+ def exit_with_message(msg, status=1)
121
+ err msg
122
+ exit(status)
123
+ end
124
+
125
+ BRANCH_NAME_FORMAT = "ticket/id/%s"
126
+
127
+ def ticket_branch(ticket_id)
128
+ BRANCH_NAME_FORMAT % ticket_id
129
+ end
130
+
131
+ def current_branch
132
+ %x(git branch -l | grep "*" | cut -d " " -f 2).strip
133
+ end
134
+
135
+ def guess_ticket
136
+ branch = current_branch
137
+ if branch =~ %r!id/(\d+)!
138
+ ticket = $1
139
+ end
140
+ end
141
+
142
+ def ticket_and_branch(options)
143
+ if options[:ticket_id]
144
+ ticket = options[:ticket_id]
145
+ branch_name = ticket_branch(ticket)
146
+ else
147
+ branch_name = current_branch
148
+ ticket = guess_ticket
149
+ end
150
+ [ticket, branch_name]
151
+ end
152
+
153
+ def response_success?(response)
154
+ code = response.code.to_i
155
+ code >= 200 && code < 300
156
+ end
157
+
158
+ # this is unnecessary hacks for multibytes charactors handling...
159
+ def mlength(s)
160
+ width = 0
161
+ cnt = 0
162
+ s.split(//u).each{|c| cnt += 1 ;width += 1 if c.length > 1 }
163
+ cnt + width
164
+ end
165
+
166
+ # this is unnecessary hacks for multibytes charactors handling...
167
+ def mljust(s, n)
168
+ return "" unless s
169
+ cnt = 0
170
+ chars = []
171
+
172
+ s.split(//u).each do |c|
173
+ next if cnt > n
174
+ chars << c
175
+ cnt += 1
176
+ cnt += 1 if c.length > 1
177
+ end
178
+ if cnt > n
179
+ chars.pop
180
+ cnt -= 1
181
+ cnt -= 1 if chars.last.length > 1
182
+ end
183
+ chars << " " * (n - cnt) if n > cnt
184
+ chars.join
185
+ end
186
+
187
+ # for 1.8.6...
188
+ def mktmpdir(prefix_suffix=nil, tmpdir=nil)
189
+ case prefix_suffix
190
+ when nil
191
+ prefix = "d"
192
+ suffix = ""
193
+ when String
194
+ prefix = prefix_suffix
195
+ suffix = ""
196
+ when Array
197
+ prefix = prefix_suffix[0]
198
+ suffix = prefix_suffix[1]
199
+ else
200
+ raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
201
+ end
202
+ tmpdir ||= Dir.tmpdir
203
+ t = Time.now.strftime("%Y%m%d")
204
+ n = nil
205
+ begin
206
+ path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
207
+ path << "-#{n}" if n
208
+ path << suffix
209
+ Dir.mkdir(path, 0700)
210
+ rescue Errno::EEXIST
211
+ n ||= 0
212
+ n += 1
213
+ retry
214
+ end
215
+
216
+ if block_given?
217
+ begin
218
+ yield path
219
+ ensure
220
+ FileUtils.remove_entry_secure path
221
+ end
222
+ else
223
+ path
224
+ end
225
+ end
226
+
227
+ def to_date(d)
228
+ Date.parse(d).strftime('%Y/%m/%d') rescue d
229
+ end
230
+
231
+ def parse_options(args)
232
+ @options = {}
233
+ @opt_parse_obj.parse!(args)
234
+ args
235
+ end
236
+
237
+ def opt_parser
238
+ OptionParser.new{|opts|
239
+ opts.banner = 'git issue <command> [ticket_id] [<args>]'
240
+ opts.on("--all", "-a", "update all paths in the index file "){ @options[:all] = true }
241
+ opts.on("--force", "-f", "force create branch"){ @options[:force] = true }
242
+ opts.on("--verbose", "-v", "show issue details"){|v| @options[:verbose] = true}
243
+ opts.on("--max-count=VALUE", "-n=VALUE", "maximum number of issues "){|v| @options[:max_count] = v.to_i}
244
+ opts.on("--oneline", "display short info"){|v| @options[:oneline] = true}
245
+ opts.on("--raw-id", "output ticket number only"){|v| @options[:raw_id] = true}
246
+ opts.on("--remote=VALUE", 'on publish, remote repository to push branch ') {|v| @options[:remote] = v}
247
+ opts.on("--onto=VALUE", 'on rebase, start new branch with HEAD equal to "newbase" ') {|v| @options[:onto] = v}
248
+
249
+ opts.on("--debug", "debug print"){@debug= true }
250
+ }
251
+
252
+ end
253
+
254
+ def puts(msg)
255
+ @sysout.puts msg
256
+ end
257
+
258
+ def err(msg)
259
+ @syserr.puts msg
260
+ end
261
+
262
+ end
263
+