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
@@ -1,308 +1,102 @@
1
+ require 'singleton'
1
2
  require 'toolshed/version'
2
3
  require 'yaml'
3
4
 
4
5
  module Toolshed
6
+ # This is responsible for loading .toolshedrc file
5
7
  class Client
6
- GITHUB_BASE_API_URL = "https://api.github.com/"
7
- PIVOTAL_TRACKER_BASE_API_URL = "https://www.pivotaltracker.com/services/v5/"
8
-
9
- # debugging options
10
- def self.debug?
11
- @debug
12
- end
13
-
14
- def self.debug=(debug)
15
- @debug = debug
16
- end
17
-
18
- def self.git_quiet
19
- @git_quiet
20
- end
21
-
22
- def self.git_quiet=(git_quiet)
23
- @git_quiet = git_quiet
24
- end
25
-
26
- def self.use_defaults
27
- @use_defaults
28
- end
29
-
30
- def self.use_defaults=(use_defaults)
31
- @use_defaults = use_defaults
32
- end
33
-
34
-
35
- # github config settings
36
- def self.github_username
37
- @github_username
38
- end
39
-
40
- def self.github_username=(username)
41
- @github_username = username
42
- end
43
-
44
- def self.github_password
45
- @github_password
46
- end
47
-
48
- def self.github_password=(password)
49
- @github_password = password
50
- end
51
-
52
- def self.github_token
53
- @github_token
54
- end
55
-
56
- def self.github_token=(token)
57
- @github_token = token
58
- end
59
-
60
- def self.pull_from_remote_name
61
- @pull_from_remote_name
62
- end
63
-
64
- def self.pull_from_remote_name=(pull_from_remote_name)
65
- @pull_from_remote_name = pull_from_remote_name
66
- end
67
-
68
- def self.pull_from_repository_user
69
- @pull_from_repository_user
70
- end
71
-
72
- def self.pull_from_repository_user=(pull_from_repository_user)
73
- @pull_from_repository_user = pull_from_repository_user
74
- end
75
-
76
- def self.pull_from_repository_name
77
- @pull_from_repository_name
78
- end
79
-
80
- def self.pull_from_repository_name=(pull_from_repository_name)
81
- @pull_from_repository_name = pull_from_repository_name
82
- end
83
-
84
- def self.push_to_repository_user
85
- @push_to_repository_user
86
- end
87
-
88
- def self.push_to_repository_user=(push_to_repository_user)
89
- @push_to_repository_user = push_to_repository_user
90
- end
91
-
92
- def self.push_to_remote_name
93
- @push_to_remote_name
94
- end
95
-
96
- def self.push_to_remote_name=(push_to_remote_name)
97
- @push_to_remote_name = push_to_remote_name
98
- end
99
-
100
- def self.use_git_submodules
101
- @use_git_submodules
102
- end
103
-
104
- def self.use_git_submodules=(use_git_submodules)
105
- @use_git_submodules = use_git_submodules
106
- end
107
-
108
- def self.git_tool
109
- (@git_tool.nil?) ? Toolshed::Git::DEFAULT_GIT_TOOL : @git_tool
110
- end
111
-
112
- def self.git_tool=(git_tool)
113
- @git_tool = git_tool
114
- end
115
-
116
-
117
- # ticket tracking information
118
-
119
- def self.pivotal_tracker_username
120
- @pivotal_tracker_username
121
- end
122
-
123
- def self.pivotal_tracker_username=(username)
124
- @pivotal_tracker_username = username
125
- end
126
-
127
- def self.pivotal_tracker_password
128
- @pivotal_tracker_password
129
- end
130
-
131
- def self.pivotal_tracker_password=(password)
132
- @pivotal_tracker_password = password
133
- end
134
-
135
- def self.default_pivotal_tracker_project_id
136
- @default_pivotal_tracker_project_id
137
- end
138
-
139
- def self.default_pivotal_tracker_project_id=(default_pivotal_tracker_project_id)
140
- @default_pivotal_tracker_project_id = default_pivotal_tracker_project_id
141
- end
142
-
143
- def self.ticket_tracker_username
144
- @ticket_tracker_username
145
- end
146
-
147
- def self.ticket_tracker_username=(username)
148
- @ticket_tracker_username = username
149
- end
150
-
151
- def self.ticket_tracker_password
152
- @ticket_tracker_password
153
- end
154
-
155
- def self.ticket_tracker_password=(password)
156
- @ticket_tracker_password = password
157
- end
158
-
159
- def self.default_ticket_tracker_project
160
- @default_ticket_tracker_project
161
- end
162
-
163
- def self.default_ticket_tracker_project=(default_ticket_tracker_project)
164
- @default_ticket_tracker_project = default_ticket_tracker_project
165
- end
166
-
167
- def self.ticket_tracking_tool
168
- @ticket_tracking_tool
169
- end
170
-
171
- def self.ticket_tracking_tool=(ticket_tracking_tool)
172
- @ticket_tracking_tool = ticket_tracking_tool
173
- end
174
-
175
- def self.ticket_tracker_owner
176
- @ticket_tracker_owner
177
- end
178
-
179
- def self.ticket_tracker_owner=(owner)
180
- @ticket_tracker_owner = owner
181
- end
182
-
183
- def self.ticket_status_for_complete
184
- @ticket_status_for_complete
185
- end
186
-
187
- def self.ticket_status_for_complete=(status)
188
- @ticket_status_for_complete = status
189
- end
190
-
191
- def self.default_pull_request_title_format
192
- @default_pull_request_title_format
193
- end
194
-
195
- def self.default_pull_request_title_format=(default_pull_request_title_format)
196
- @default_pull_request_title_format = default_pull_request_title_format
197
- end
198
-
199
- # time tracking configuration
200
-
201
- def self.time_tracking_tool
202
- @time_tracking_tool
203
- end
204
-
205
- def self.time_tracking_tool=(time_tracking_tool)
206
- @time_tracking_tool = time_tracking_tool
207
- end
208
-
209
- def self.time_tracking_username
210
- @time_tracking_username
211
- end
212
-
213
- def self.time_tracking_username=(time_tracking_username)
214
- @time_tracking_username = time_tracking_username
215
- end
216
-
217
- def self.time_tracking_password
218
- @time_tracking_password
219
- end
220
-
221
- def self.time_tracking_password=(time_tracking_password)
222
- @time_tracking_password = time_tracking_password
223
- end
224
-
225
- def self.time_tracking_owner
226
- @time_tracking_owner
227
- end
228
-
229
- def self.time_tracking_owner=(time_tracking_owner)
230
- @time_tracking_owner = time_tracking_owner
231
- end
232
-
233
- def self.time_tracking_default_project_id
234
- @time_tracking_default_project_id
235
- end
236
-
237
- def self.time_tracking_default_project_id=(time_tracking_default_project_id)
238
- @time_tracking_default_project_id = time_tracking_default_project_id
239
- end
240
-
241
- def self.load_credentials_if_necessary
242
- load_credentials unless credentials_loaded?
243
- end
244
-
245
- def self.config_path
246
- ENV['TOOLSHED_CONFIG'] || '~/.toolshedrc'
247
- end
8
+ include Singleton
9
+
10
+ attr_accessor :debug,
11
+ :git_quiet,
12
+ :use_defaults,
13
+ :github_username,
14
+ :github_password,
15
+ :github_token,
16
+ :use_defaults,
17
+ :pull_from_repository_user,
18
+ :pull_from_repository_name,
19
+ :pull_from_remote_name,
20
+ :push_to_repository_user,
21
+ :push_to_remote_name,
22
+ :use_git_submodules,
23
+ :git_tool,
24
+ :pivotal_tracker_username,
25
+ :pivotal_tracker_password,
26
+ :default_pivotal_tracker_project_id,
27
+ :ticket_tracker_username,
28
+ :ticket_tracker_password,
29
+ :default_ticket_tracker_project,
30
+ :ticket_tracking_tool,
31
+ :ticket_tracker_owner,
32
+ :ticket_status_for_complete,
33
+ :default_pull_request_title_format,
34
+ :time_tracking_tool,
35
+ :time_tracking_username,
36
+ :time_tracking_password,
37
+ :time_tracking_owner,
38
+ :time_tracking_default_project_id,
39
+ :load_credentials_if_necessary,
40
+ :log_path,
41
+ :credentials_loaded_from
42
+
43
+ GITHUB_BASE_API_URL = 'https://api.github.com/'
44
+ PIVOTAL_TRACKER_BASE_API_URL = 'https://www.pivotaltracker.com/services/v5/'
45
+
46
+ class << self
47
+ def config_path
48
+ ENV['TOOLSHED_CONFIG'] || '~/.toolshedrc'
49
+ end
248
50
 
249
- def self.load_credentials(path = config_path)
250
- credentials = Client.read_credenials(config_path)
251
- self.github_username ||= credentials['github_username']
252
- self.github_password ||= credentials['github_password']
253
- self.github_token ||= credentials['github_token']
254
- self.pivotal_tracker_username ||= credentials['pivotal_tracker_username']
255
- self.pivotal_tracker_password ||= credentials['pivotal_tracker_password']
256
- self.default_pivotal_tracker_project_id ||= credentials['default_pivotal_tracker_project_id']
257
- self.ticket_tracker_username ||= credentials['ticket_tracker_username']
258
- self.ticket_tracker_password ||= credentials['ticket_tracker_password']
259
- self.ticket_tracker_owner ||= credentials['ticket_tracker_owner']
260
- self.ticket_status_for_complete ||= credentials['ticket_status_for_complete']
261
- self.default_ticket_tracker_project ||= credentials['default_ticket_tracker_project']
262
- self.pull_from_remote_name ||= credentials['pull_from_remote_name']
263
- self.pull_from_repository_user ||= credentials['pull_from_repository_user']
264
- self.pull_from_repository_name ||= credentials['pull_from_repository_name']
265
- self.push_to_repository_user ||= credentials['push_to_repository_user']
266
- self.push_to_remote_name ||= credentials['push_to_remote_name']
267
- self.ticket_tracking_tool ||= credentials['ticket_tracking_tool']
268
- self.use_git_submodules ||= credentials['use_git_submodules']
269
- self.git_tool ||= credentials['git_tool']
270
- self.time_tracking_username ||= credentials['time_tracking_username']
271
- self.time_tracking_password ||= credentials['time_tracking_password']
272
- self.time_tracking_owner ||= credentials['time_tracking_owner']
273
- self.time_tracking_default_project_id ||= credentials['time_tracking_default_project_id']
274
- self.time_tracking_tool ||= credentials['time_tracking_tool']
275
- self.git_quiet ||= (credentials['git_quiet']) ? '&> /dev/null' : ''
276
- self.use_defaults ||= credentials['use_defaults']
277
- self.default_pull_request_title_format ||= credentials['default_pull_request_title_format']
278
- @credentials_loaded = true
279
- end
51
+ def load_credentials # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
52
+ credentials = Client.read_credenials
53
+ instance.github_username ||= credentials['github_username']
54
+ instance.github_password ||= credentials['github_password']
55
+ instance.github_token ||= credentials['github_token']
56
+ instance.pivotal_tracker_username ||= credentials['pivotal_tracker_username']
57
+ instance.pivotal_tracker_password ||= credentials['pivotal_tracker_password']
58
+ instance.default_pivotal_tracker_project_id ||= credentials['default_pivotal_tracker_project_id'] # rubocop:disable Metrics/LineLength
59
+ instance.ticket_tracker_username ||= credentials['ticket_tracker_username']
60
+ instance.ticket_tracker_password ||= credentials['ticket_tracker_password']
61
+ instance.ticket_tracker_owner ||= credentials['ticket_tracker_owner']
62
+ instance.ticket_status_for_complete ||= credentials['ticket_status_for_complete']
63
+ instance.default_ticket_tracker_project ||= credentials['default_ticket_tracker_project'] # rubocop:disable Metrics/LineLength
64
+ instance.pull_from_remote_name ||= credentials['pull_from_remote_name']
65
+ instance.pull_from_repository_user ||= credentials['pull_from_repository_user']
66
+ instance.pull_from_repository_name ||= credentials['pull_from_repository_name']
67
+ instance.push_to_repository_user ||= credentials['push_to_repository_user']
68
+ instance.push_to_remote_name ||= credentials['push_to_remote_name']
69
+ instance.ticket_tracking_tool ||= credentials['ticket_tracking_tool'] # rubocop:disable Metrics/LineLength
70
+ instance.use_git_submodules ||= credentials['use_git_submodules']
71
+ instance.git_tool ||= credentials['git_tool']
72
+ instance.time_tracking_username ||= credentials['time_tracking_username']
73
+ instance.time_tracking_password ||= credentials['time_tracking_password']
74
+ instance.time_tracking_owner ||= credentials['time_tracking_owner']
75
+ instance.time_tracking_default_project_id ||= credentials['time_tracking_default_project_id'] # rubocop:disable Metrics/LineLength
76
+ instance.time_tracking_tool ||= credentials['time_tracking_tool']
77
+ instance.git_quiet ||= (credentials['git_quiet']) ? '&> /dev/null' : ''
78
+ instance.use_defaults ||= credentials['use_defaults']
79
+ instance.default_pull_request_title_format ||= credentials['default_pull_request_title_format'] # rubocop:disable Metrics/LineLength
80
+ instance.log_path ||= credentials['log_path']
81
+ instance.credentials_loaded_from ||= load_credentials_from
82
+ end
280
83
 
281
- def self.credentials_loaded?
282
- (@credentials_loaded ||= false)
283
- end
84
+ def read_credenials
85
+ YAML.load_file(File.expand_path(load_credentials_from))
86
+ end
284
87
 
285
- def self.read_credenials(path = Client.config_path)
286
- begin
88
+ def load_credentials_from
287
89
  dir = Dir.pwd
288
- while File.expand_path(dir) != '/' do
289
- if (File.exists? "#{dir}/.toolshedrc")
290
- loaded_from_path = "#{dir}/.toolshedrc"
291
- break
292
- elsif (File.exists? "#{dir}/.toolshed")
293
- loaded_from_path = "#{dir}/.toolshed"
294
- warn "[DEPRECATION] `.toolshed` file is being deprecated. Please use a `.toolshedrc` file instead."
295
- break
90
+ while File.expand_path(dir) != '/'
91
+ unless File.exist?("#{dir}/.toolshedrc")
92
+ dir = File.join dir, '..'
93
+ next
296
94
  end
297
-
298
- dir = File.join dir, '..'
95
+ credentials_loaded_from = "#{dir}/.toolshedrc"
96
+ break
299
97
  end
300
98
 
301
- puts "Credentials loaded from #{File.absolute_path(loaded_from_path)}"
302
- credentials = YAML.load_file(File.expand_path(loaded_from_path))
303
- rescue => error
304
- puts "Error loading your credentials: #{error.message}"
305
- exit 1
99
+ credentials_loaded_from
306
100
  end
307
101
  end
308
102
  end
@@ -1,66 +1,108 @@
1
+ require 'toolshed/cli'
2
+ require 'optparse'
3
+
1
4
  module Toolshed
2
5
  module Commands
6
+ # Base class for all commands responsible for common methods
3
7
  class Base
4
- def initialize(options={})
8
+ def initialize(_options = {})
9
+ end
10
+
11
+ def self.parse(command, cli_options = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/LineLength, Metrics/CyclomaticComplexity
12
+ options = {}
13
+ options_parser = OptionParser.new do |opts|
14
+ opts.banner = cli_options[:banner] if cli_options[:banner]
15
+ cli_options[:options].each do |option_name, option_variables|
16
+ letter_map = ('a'..'z').map { |letter| letter }
17
+ short_on = (option_variables[:short_on]) ? option_variables[:short_on] : letter_map[rand(letter_map.length)] # rubocop:disable Metrics/LineLength
18
+ on = (option_variables[:on]) ? option_variables[:on] : "--#{option_name.to_s.split('_').join('-')} [ARG]" # rubocop:disable Metrics/LineLength
19
+ opts.on(short_on, on) do |opt|
20
+ value = (option_variables[:default].nil?) ? opt : option_variables[:default]
21
+ options.merge!(option_name => value)
22
+ end
23
+ end
24
+ end
25
+ options_parser.order! if options_parser
26
+ begin
27
+ cli = Toolshed::CLI.new
28
+ cli.execute(command, ARGV, options)
29
+ rescue Toolshed::CommandNotFound => e
30
+ Toolshed.logger.fatal e.message
31
+ Toolshed.die
32
+ end
5
33
  end
6
34
 
7
- def read_user_input(message, options={})
8
- return options[:default] if Toolshed::Client.use_defaults
9
- prompt_user_input(message, options)
35
+ def read_user_input(message, options = {})
36
+ return options[:default] if Toolshed::Client.instance.use_defaults
37
+ required = options[:required] || false
38
+ value = ''
39
+ if required
40
+ value = prompt_user_input(message, options) while value.empty?
41
+ else
42
+ value = prompt_user_input(message, options)
43
+ end
44
+ value
10
45
  end
11
46
 
12
- def read_user_input_title(message, options={})
13
- return options[:title] if options.has_key?(:title)
47
+ def read_user_input_title(message, options = {})
48
+ return options[:title] if options.key?(:title)
14
49
  read_user_input(message, options)
15
50
  end
16
51
 
17
- def read_user_input_body(message, options={})
18
- return options[:body] if options.has_key?(:body)
52
+ def read_user_input_body(message, options = {})
53
+ return options[:body] if options.key?(:body)
19
54
  read_user_input(message, options)
20
55
  end
21
56
 
22
57
  def use_ticket_tracker_project_id(options)
23
- options
24
- options.merge!({
58
+ options.merge!(
25
59
  ticket_tracker_const: 'USE_PROJECT_ID',
26
60
  type: :project_id,
27
61
  default_method: 'default_pivotal_tracker_project_id',
28
- default_message: "Project ID (Default: #{Toolshed::Client.default_pivotal_tracker_project_id}):",
29
- })
30
- options = use_ticket_tracker_by_type(options)
62
+ default_message: "Project ID (Default: #{Toolshed::Client.instance.default_pivotal_tracker_project_id}):" # rubocop:disable Metrics/LineLength
63
+ )
64
+ use_ticket_tracker_by_type(options)
31
65
  end
32
66
 
33
67
  def use_ticket_tracker_project_name(options)
34
- options.merge!({
68
+ options.merge!(
35
69
  ticket_tracker_const: 'USE_PROJECT_NAME',
36
70
  type: :project,
37
71
  default_method: 'default_ticket_tracker_project',
38
- default_message: "Project Name (Default: #{Toolshed::Client.default_ticket_tracker_project}):",
39
- })
72
+ default_message: "Project Name (Default: #{Toolshed::Client.instance.default_ticket_tracker_project}):" # rubocop:disable Metrics/LineLength
73
+ )
40
74
  use_ticket_tracker_by_type(options)
41
75
  end
42
76
 
43
77
  def use_ticket_tracker_by_type(options)
44
- use_field = Object.const_get("#{ticket_tracker_class}::#{options[:ticket_tracker_const]}") rescue false
78
+ use_field = Object.const_get("#{ticket_tracker_class}::#{options[:ticket_tracker_const]}") rescue false # rubocop:disable Metrics/LineLength, Style/RescueModifier
45
79
  if use_field
46
- ticket_tracker_response = read_user_input(options[:default_message],
47
- options.merge!({ default: Toolshed::Client.send(options[:default_method]) })
80
+ ticket_tracker_response = read_user_input(
81
+ options[:default_message],
82
+ options.merge!(
83
+ default: Toolshed::Client.instance.send(options[:default_method])
84
+ )
48
85
  )
49
- options.merge!({ options[:type] => ticket_tracker_response })
86
+ options.merge!(options[:type] => ticket_tracker_response)
50
87
  end
51
88
  options
52
89
  end
53
90
 
54
91
  def use_project_id
55
- Object.const_get("#{ticket_tracker_class}::USE_PROJECT_ID") rescue false
92
+ Object.const_get("#{ticket_tracker_class}::USE_PROJECT_ID") rescue false # rubocop:disable Style/RescueModifier, Metrics/LineLength
56
93
  end
57
94
 
58
95
  private
59
96
 
60
- def prompt_user_input(message, options)
97
+ def prompt_user_input(message, options) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
98
+ required = options[:required] || false
61
99
  puts message
62
100
  value = $stdin.gets.chomp
63
- value.empty? ? options[:default] : value
101
+ if required && value.nil? || value.empty?
102
+ puts 'Value is required please try again.'
103
+ return value
104
+ end
105
+ (value.nil? || value.empty?) ? options[:default] : value
64
106
  end
65
107
  end
66
108
  end