toolshed 1.0.2 → 1.0.3

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