toolshed 1.0.2 → 1.0.3

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +11 -0
  4. data/.toolshedrc.sample +32 -0
  5. data/README.md +159 -2
  6. data/Rakefile +3 -3
  7. data/bin/toolshed +6 -1
  8. data/lib/toolshed.rb +38 -28
  9. data/lib/toolshed/base.rb +33 -11
  10. data/lib/toolshed/cli.rb +30 -38
  11. data/lib/toolshed/client.rb +87 -293
  12. data/lib/toolshed/commands/base.rb +65 -23
  13. data/lib/toolshed/commands/checkout_branch.rb +15 -2
  14. data/lib/toolshed/commands/create_branch.rb +34 -29
  15. data/lib/toolshed/commands/create_pivotal_tracker_note.rb +9 -3
  16. data/lib/toolshed/commands/create_pull_request.rb +115 -68
  17. data/lib/toolshed/commands/create_ticket_comment.rb +17 -1
  18. data/lib/toolshed/commands/delete_branch.rb +34 -3
  19. data/lib/toolshed/commands/get_daily_time_update.rb +20 -3
  20. data/lib/toolshed/commands/list_branches.rb +16 -5
  21. data/lib/toolshed/commands/push_branch.rb +28 -9
  22. data/lib/toolshed/commands/rename_branch.rb +29 -0
  23. data/lib/toolshed/commands/ssh.rb +44 -3
  24. data/lib/toolshed/commands/ticket_information.rb +30 -4
  25. data/lib/toolshed/commands/update_pivotal_tracker_story_status.rb +9 -3
  26. data/lib/toolshed/commands/update_ticket_status.rb +8 -2
  27. data/lib/toolshed/entry_point.rb +89 -0
  28. data/lib/toolshed/git.rb +59 -0
  29. data/lib/toolshed/git/branch.rb +224 -0
  30. data/lib/toolshed/git/github.rb +45 -57
  31. data/lib/toolshed/git/validator.rb +14 -0
  32. data/lib/toolshed/logger.rb +46 -0
  33. data/lib/toolshed/password.rb +11 -6
  34. data/lib/toolshed/server_administration/ssh.rb +4 -2
  35. data/lib/toolshed/ticket_tracking/jira.rb +8 -6
  36. data/lib/toolshed/ticket_tracking/pivotal_tracker.rb +8 -6
  37. data/lib/toolshed/time_tracking/harvest.rb +8 -14
  38. data/lib/toolshed/version.rb +25 -1
  39. data/test/commands/checkout_branch_test.rb +11 -7
  40. data/test/commands/create_branch_test.rb +29 -24
  41. data/test/commands/create_pull_request_test.rb +39 -31
  42. data/test/commands/delete_branch_test.rb +35 -25
  43. data/test/commands/get_daily_time_update_test.rb +8 -8
  44. data/test/commands/push_branch_test.rb +27 -15
  45. data/test/commands/rename_branch_test.rb +59 -0
  46. data/test/git/git_helper.rb +5 -5
  47. data/test/git/git_test.rb +36 -31
  48. data/test/git/github_test.rb +9 -46
  49. data/test/helper.rb +11 -11
  50. data/test/server_administration/ssh_test.rb +1 -0
  51. data/test/ticket_tracking/jira_test.rb +18 -16
  52. data/test/time_tracking/harvest_test.rb +8 -6
  53. data/toolshed.gemspec +23 -20
  54. metadata +95 -46
  55. data/bin/toolshed.rb +0 -261
  56. data/lib/toolshed/git/git.rb +0 -119
@@ -0,0 +1,224 @@
1
+ require 'toolshed/git'
2
+
3
+ module Toolshed
4
+ class Git
5
+ class Branch < Toolshed::Git
6
+ def initialize(options = {})
7
+ super(options)
8
+ end
9
+
10
+ # class methods
11
+
12
+ class << self
13
+ def ask_which_branch(branch_names)
14
+ selected_branch_name = ''
15
+ choose do |menu|
16
+ menu.prompt = "Multiple branches matched input branch name. Which branch are you looking for? "
17
+
18
+ branch_names.each do |branch_name|
19
+ menu.choice(branch_name.to_sym, branch_name) do |branch_name|
20
+ selected_branch_name = branch_name
21
+ end
22
+ end
23
+ end
24
+ selected_branch_name.to_s
25
+ end
26
+
27
+ def from
28
+ `git rev-parse --abbrev-ref --symbolic-full-name @{u}`.split('/')[-1].strip
29
+ end
30
+
31
+ def checkout(checkout_branch_name)
32
+ Toolshed.logger.info ''
33
+ Toolshed.logger.info "Looking for branch #{checkout_branch_name}"
34
+ actual_branch_name = Toolshed::Git::Branch.name_from_substring(checkout_branch_name)
35
+ Toolshed.logger.info "Switching to branch #{actual_branch_name}"
36
+ result = Toolshed::Base.wait_for_command("git checkout #{actual_branch_name} #{Toolshed::Client.instance.git_quiet}")
37
+ if /.*Your local changes to the following files would be overwritten by checkout.*/.match(result[:stderr][0])
38
+ Toolshed.logger.fatal "Unable to checkout branch due to the following error(s) #{result[:all].join(', ')}"
39
+ Toolshed.die
40
+ end
41
+ Toolshed::Base.wait_for_command(Toolshed::Git.git_submodule_command) unless Toolshed::Git.git_submodule_command.empty?
42
+ Toolshed.logger.info "Switched to branch #{actual_branch_name}"
43
+ actual_branch_name
44
+ end
45
+
46
+ def name
47
+ branch = Toolshed::Git::Branch.new
48
+ branch.name
49
+ end
50
+
51
+ def name_from_substring(substring)
52
+ branches = Toolshed::Base.wait_for_command("git branch | grep \"#{substring}\"")
53
+ branch_names = branches[:all].map { |branch_name| branch_name.gsub('*', '').strip }
54
+
55
+ return substring if branch_names.length == 0
56
+ return branch_names.first if branch_names.length == 1
57
+ Toolshed::Git::Branch.ask_which_branch(branch_names)
58
+ end
59
+ end
60
+
61
+ # instance methods
62
+
63
+ def create
64
+ validator.validate!(self)
65
+ Toolshed.logger.info ''
66
+
67
+ new_branch_name = Toolshed::Git.clean_branch_name(to_remote_branch_name)
68
+ Toolshed.logger.info "Creating branch #{new_branch_name} from #{from_remote_name}/#{from_remote_branch_name}"
69
+
70
+ remote_update
71
+ results = Toolshed::Base.wait_for_command("git checkout -b #{new_branch_name} #{from_remote_name}/#{from_remote_branch_name} #{Toolshed::Client.instance.git_quiet}")
72
+ results[:all].each do |out|
73
+ if out.match(/.*fatal.*/)
74
+ Toolshed.logger.fatal out
75
+ Toolshed.die
76
+ else
77
+ Toolshed.logger.info out
78
+ end
79
+ end
80
+ Toolshed::Base.wait_for_command(Toolshed::Git.git_submodule_command) unless Toolshed::Git.git_submodule_command.empty?
81
+
82
+ Toolshed.logger.info ''
83
+ Toolshed.logger.info "Pushing branch #{new_branch_name} to #{from_remote_name}/#{from_remote_branch_name}."
84
+ self.passed_branch_name = new_branch_name
85
+ push
86
+
87
+ Toolshed.logger.info ''
88
+ Toolshed.logger.info "Branch #{new_branch_name} has been created from #{from_remote_name}/#{from_remote_branch_name}."
89
+ end
90
+
91
+ def delete(delete_branch_name)
92
+ actual_branch_name = Toolshed::Git::Branch.name_from_substring(delete_branch_name)
93
+ Toolshed.logger.info ''
94
+ Toolshed.logger.info "Deleting branch '#{actual_branch_name}'"
95
+ if actual_branch_name == name
96
+ Toolshed.logger.info 'Checking out master branch'
97
+ Toolshed.logger.info ''
98
+ Toolshed::Git::Branch.checkout('master')
99
+ end
100
+
101
+ delete_local(actual_branch_name)
102
+ delete_remote(actual_branch_name)
103
+ Toolshed.logger.info ''
104
+ Toolshed.logger.info "Deleted branch #{actual_branch_name}"
105
+ end
106
+
107
+ def delete_local(local_branch_name)
108
+ unless local.map { |local_branch| local_branch[:branch_name] }.include?(local_branch_name)
109
+ Toolshed.logger.warn "Unable to delete '#{local_branch_name}' from local as it does not exist skipping"
110
+ return
111
+ end
112
+
113
+ results = Toolshed::Base.wait_for_command("git branch -D #{local_branch_name}")
114
+ results[:all].each do |result|
115
+ Toolshed.logger.info result.rstrip
116
+ end
117
+ end
118
+
119
+ def delete_remote(remote_branch_name)
120
+ unless remote.include?(remote_branch_name)
121
+ Toolshed.logger.warn "Unable to delete '#{remote_branch_name}' from remote as it does not exist skipping"
122
+ return
123
+ end
124
+
125
+ Toolshed.logger.info "Deleting #{remote_branch_name} from remote"
126
+ results = Toolshed::Base.wait_for_command("git push #{Toolshed::Client.instance.push_to_remote_name} --delete #{remote_branch_name}")
127
+ results[:all].each do |result|
128
+ Toolshed.logger.info result.rstrip
129
+ end
130
+ end
131
+
132
+ def list
133
+ remote_update
134
+ list_local
135
+ list_remote
136
+ end
137
+
138
+ def list_local
139
+ Toolshed.logger.info ''
140
+ Toolshed.logger.info 'Local Branches'
141
+ Toolshed.logger.info ''
142
+ current_branch_name = name
143
+ local.each do |local_branch|
144
+ Toolshed.logger.info "#{local_branch[:branch_name]} #{local_branch[:branch_info]}"
145
+ end
146
+ end
147
+
148
+ def list_remote
149
+ Toolshed.logger.info ''
150
+ Toolshed.logger.info 'Remote Branches'
151
+ Toolshed.logger.info ''
152
+ remote.each do |remote_branch|
153
+ Toolshed.logger.info remote_branch
154
+ end
155
+ end
156
+
157
+ def local
158
+ @local ||= begin
159
+ local_branches = []
160
+ results = Toolshed::Base.wait_for_command('git branch -avv')
161
+ results[:stdout].each do |stdout|
162
+ next if /remotes.*/.match(stdout) || /HEAD.*/.match(stdout)
163
+ branch_name = /.*\s{3,}/.match(stdout)[0]
164
+ branch_name = branch_name.gsub('*', '')
165
+ branch_info_match = /\[[a-z].*\]/.match(stdout)
166
+ branch_info = ''
167
+ branch_info = branch_info_match[0] unless branch_info_match.nil?
168
+ local_branches << { branch_name: branch_name.lstrip.rstrip, branch_info: branch_info.lstrip.rstrip }
169
+ end
170
+ local_branches
171
+ end
172
+ end
173
+
174
+ def name
175
+ (passed_branch_name.nil? || passed_branch_name.empty?) ? `git rev-parse --abbrev-ref HEAD`.strip : Toolshed::Git::Branch.name_from_substring(passed_branch_name) # rubocop:disable Metrics/LineLength
176
+ end
177
+
178
+ def push
179
+ Toolshed.logger.info "Pushing #{name}"
180
+ if (name.nil? || name.empty?) && (!passed_branch_name.nil? && !passed_branch_name.empty?)
181
+ Toolshed.logger.fatal "Branch #{passed_branch_name} was not found. Unable to push branch."
182
+ Toolshed.die
183
+ end
184
+ result = Toolshed::Base.wait_for_command("git push #{to_remote_name} #{name} #{force} #{Toolshed::Client.instance.git_quiet}")
185
+ result[:all].each do |stdout|
186
+ Toolshed.logger.info stdout
187
+ end
188
+ Toolshed.logger.info 'Everything up-to-date' if result[:stdout].empty? && result[:stderr].empty?
189
+ Toolshed.logger.info "#{name} has been pushed"
190
+ end
191
+
192
+ def remote
193
+ remote_branches = []
194
+ results = Toolshed::Base.wait_for_command('git branch -avv')
195
+ results[:stdout].each do |stdout|
196
+ next unless /remotes\/#{from_remote_name}.*/.match(stdout)
197
+ next if /.*HEAD.*/.match(stdout)
198
+ matches = /([^\s]+)/.match(stdout)
199
+ remote_branches << matches[0].gsub("remotes/#{from_remote_name}/", '')
200
+ end
201
+ remote_branches
202
+ end
203
+
204
+ def rename(new_branch_name)
205
+ current_branch_name = passed_branch_name
206
+ results = Toolshed::Base.wait_for_command("git branch -m #{passed_branch_name} #{new_branch_name}")
207
+ results[:all].each do |out|
208
+ matches = /(error.*|fatal.*)/.match(out)
209
+ if matches.length > 0
210
+ Toolshed.logger.fatal out
211
+ Toolshed.die("Unable to proceed supplied branch '#{current_branch_name}' does not exist in local repository.")
212
+ else
213
+ Toolshed.logger.info out
214
+ end
215
+ end
216
+ self.passed_branch_name = new_branch_name
217
+ push
218
+ delete_remote(current_branch_name)
219
+ Toolshed.logger.info ''
220
+ Toolshed.logger.info "Deleted branch #{current_branch_name}"
221
+ end
222
+ end
223
+ end
224
+ end
@@ -1,98 +1,86 @@
1
+ require 'toolshed/git/branch'
2
+ require 'toolshed/git'
3
+
1
4
  module Toolshed
2
- module Git
3
- class Github < Base
4
- extend Toolshed::Git
5
+ class Git
6
+ # Class created to handle specific git tasks related to github
7
+ class Github < Toolshed::Git
5
8
  include HTTParty
6
9
 
7
10
  attr_accessor :default_options
8
11
 
9
- def initialize(options={})
12
+ def initialize(options = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
10
13
  super(options)
11
14
 
12
- username = Toolshed::Client::github_username
13
- password = Toolshed::Client::github_password
14
- token = Toolshed::Client::github_token
15
-
16
- unless (options[:username].nil?)
17
- username = options[:username]
18
- end
15
+ username = Toolshed::Client.instance.github_username
16
+ password = Toolshed::Client.instance.github_password
17
+ token = Toolshed::Client.instance.github_token
19
18
 
20
- unless (options[:password].nil?)
21
- password = options[:password]
22
- end
23
-
24
- unless (token.nil?)
19
+ username = options[:username] unless options[:username].nil?
20
+ password = options[:password] unless options[:password].nil?
21
+ unless token.nil?
25
22
  username = token
26
23
  password = nil
27
24
  end
28
25
 
29
- unless (options[:token].nil?)
26
+ unless options[:token].nil?
30
27
  username = options[:token]
31
28
  password = nil
32
29
  end
33
30
 
34
31
  @auth = { username: username, password: password }
35
32
  self.default_options = {
36
- :headers => {
37
- "User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17",
33
+ headers: {
34
+ 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17' # rubocop:disable Metrics/LineLength
38
35
  },
39
- basic_auth: @auth,
36
+ basic_auth: @auth
40
37
  }
41
38
  end
42
39
 
43
- def create_pull_request(title, body, options={})
44
- options.merge!(self.default_options)
45
- options.merge!({
46
- body: {
40
+ def create_pull_request(title, body, options = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/LineLength
41
+ options.merge!(default_options)
42
+ options.merge!(
43
+ body:
44
+ {
47
45
  title: title,
48
- body: body,
49
- head: "#{Toolshed::Client.github_username}:#{Toolshed::Git::Base.branch_name}",
50
- base: Toolshed::Git::Base.branched_from
46
+ body: body,
47
+ head: "#{Toolshed::Client.instance.github_username}:#{Toolshed::Git::Branch.name}", # rubocop:disable Metrics/LineLength
48
+ base: Toolshed::Git::Branch.from
51
49
  }.to_json
52
- })
53
-
54
- response = HTTParty.post("#{Toolshed::Client::GITHUB_BASE_API_URL}repos/#{Toolshed::Client.pull_from_repository_user}/#{Toolshed::Client.pull_from_repository_name}/pulls", options).response
50
+ )
51
+ display_options = Marshal.load(Marshal.dump(options))
52
+ display_options[:basic_auth][:password] = '********'
53
+ Toolshed.logger.info "Creating pull request with the following options: #{display_options.inspect}" # rubocop:disable Metrics/LineLength
54
+ response = HTTParty.post("#{Toolshed::Client::GITHUB_BASE_API_URL}repos/#{Toolshed::Client.instance.pull_from_repository_user}/#{Toolshed::Client.instance.pull_from_repository_name}/pulls", options).response # rubocop:disable Metrics/LineLength
55
55
  response = JSON.parse(response.body)
56
- if (response["errors"].nil?)
56
+ if response['errors'].nil?
57
57
  response
58
58
  else
59
- raise "validation errors #{response.inspect}"
59
+ fail "Validation errors #{response.inspect}"
60
60
  end
61
61
  end
62
62
 
63
- def list_branches(options={})
64
- options.merge!(self.default_options)
65
-
66
- response = HTTParty.get("#{Toolshed::Client::GITHUB_BASE_API_URL}repos/#{Toolshed::Client.github_username}/#{Toolshed::Client.pull_from_repository_name}/branches", options).response
67
- response = JSON.parse(response.body)
68
- end
69
-
70
63
  def self.username
71
- username = Toolshed::Client::github_username
72
- if (username.nil?)
73
- # prompt to ask for username
74
- puts "Github username? "
75
- username = $stdin.gets.chomp.strip
76
- end
64
+ username = Toolshed::Client.instance.github_username
65
+ return username unless username.nil?
77
66
 
78
- return username
67
+ puts 'Github username? '
68
+ $stdin.gets.chomp.strip
79
69
  end
80
70
 
81
71
  def self.password
82
- password = Toolshed::Client::github_password
83
- if (password.nil?)
84
- # prompt to ask for password
85
- system "stty -echo"
86
- puts "Github password? "
87
- password = $stdin.gets.chomp.strip
88
- system "stty echo"
89
- end
90
-
91
- return password
72
+ password = Toolshed::Client.instance.github_password
73
+ return password unless password.nil?
74
+
75
+ system 'stty -echo'
76
+ puts 'Github password? '
77
+ password = $stdin.gets.chomp.strip
78
+ system 'stty echo'
79
+ password
92
80
  end
93
81
 
94
82
  def self.create_instance
95
- Toolshed::Git::Github.new({ username: Toolshed::Git::Github.username, password: Toolshed::Git::Github.password })
83
+ Toolshed::Git::Github.new(username: Toolshed::Git::Github.username, password: Toolshed::Git::Github.password) # rubocop:disable Metrics/LineLength
96
84
  end
97
85
  end
98
86
  end
@@ -0,0 +1,14 @@
1
+ require 'veto'
2
+
3
+ module Toolshed
4
+ class Git
5
+ class Validator
6
+ include Veto.validator
7
+
8
+ validates :from_remote_name, :presence => true
9
+ validates :from_remote_branch_name, :presence => true
10
+ validates :to_remote_name, :presence => true
11
+ validates :to_remote_branch_name, :presence => true
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,46 @@
1
+ require 'singleton'
2
+ require 'term/ansicolor'
3
+ require 'logger'
4
+
5
+ class Toolshed::Logger
6
+ include Singleton
7
+ include Term::ANSIColor
8
+
9
+ attr_accessor :loggers
10
+
11
+ def self.create(options = {})
12
+ instance.loggers = []
13
+ log_sources = options[:log_sources] || [STDOUT]
14
+ log_sources.each do |log_source|
15
+ instance.loggers << Logger.new(log_source)
16
+ end
17
+ end
18
+
19
+ def add_log_source(source)
20
+ loggers << Logger.new(source)
21
+ end
22
+
23
+ def debug(message)
24
+ loggers.each do |logger|
25
+ logger.debug(message)
26
+ end
27
+ end
28
+
29
+ def fatal(message)
30
+ loggers.each do |logger|
31
+ logger.info(red(message))
32
+ end
33
+ end
34
+
35
+ def info(message)
36
+ loggers.each do |logger|
37
+ logger.info(green(message))
38
+ end
39
+ end
40
+
41
+ def warn(message)
42
+ loggers.each do |logger|
43
+ logger.warn(yellow(message))
44
+ end
45
+ end
46
+ end
@@ -8,7 +8,8 @@ module Toolshed
8
8
  end
9
9
 
10
10
  def read_user_input_password(type, prompt_message='Password:')
11
- unless self.send(type).blank?
11
+ value = self.send(type)
12
+ unless value.nil? || value.empty?
12
13
  read_password_from_configuration(type)
13
14
  else
14
15
  prompt_user_to_input_password(prompt_message)
@@ -31,11 +32,15 @@ module Toolshed
31
32
  end
32
33
 
33
34
  def read_password_from_configuration(type)
34
- credentials = Toolshed::Client.read_credenials
35
- if credentials[self.send(type)]
36
- credentials[self.send(type)]
37
- else
38
- self.send(type)
35
+ begin
36
+ credentials = Toolshed::Client.read_credenials
37
+ if credentials[self.send(type)]
38
+ credentials[self.send(type)]
39
+ else
40
+ self.send(type)
41
+ end
42
+ rescue => e
43
+ puts e.inspect
39
44
  end
40
45
  end
41
46
  end