git-issue 0.7.0

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