git-process-lib 2.0.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -33,8 +33,14 @@ module GitProc
33
33
  end
34
34
  @logger.level = log_level.nil? ? GitLogger::WARN : log_level
35
35
  @logger.datetime_format = '%Y-%m-%d %H:%M:%S'
36
- @logger.formatter = proc do |_, _, _, msg|
37
- "#{msg}\n"
36
+ @logger.formatter = proc do |severity, datetime, progname, msg|
37
+ if progname.nil?
38
+ m = "#{msg}\n"
39
+ else
40
+ m = "#{progname} => #{msg}\n"
41
+ end
42
+
43
+ @logger.debug? ? "[#{'%-5.5s' % severity}] #{datetime} - #{m}" : m
38
44
  end
39
45
  end
40
46
 
@@ -45,38 +51,27 @@ module GitProc
45
51
 
46
52
 
47
53
  def debug(msg = nil, &block)
48
- if msg.nil?
49
- @logger.debug(&block)
50
- else
51
- @logger.debug(msg)
52
- end
54
+ @logger.debug(msg, &block)
53
55
  end
54
56
 
55
57
 
56
58
  def info(msg = nil, &block)
57
- if msg.nil?
58
- @logger.info(&block)
59
- else
60
- @logger.info(msg)
61
- end
59
+ @logger.info(msg, &block)
62
60
  end
63
61
 
64
62
 
65
63
  def warn(msg = nil, &block)
66
- if msg.nil?
67
- @logger.send(:warn, &block)
68
- else
69
- @logger.send(:warn, msg)
70
- end
64
+ @logger.warn(msg, &block)
71
65
  end
72
66
 
73
67
 
74
68
  def error(msg = nil, &block)
75
- if msg.nil?
76
- @logger.error(&block)
77
- else
78
- @logger.error(msg)
79
- end
69
+ @logger.error(msg, &block)
70
+ end
71
+
72
+
73
+ def fatal(msg = nil, &block)
74
+ @logger.fatal(msg, &block)
80
75
  end
81
76
 
82
77
  end
@@ -107,13 +107,20 @@ module GitProc
107
107
  end
108
108
 
109
109
 
110
+ # noinspection RubyLocalVariableNamingConvention
110
111
  def should_remove_master?
111
- my_branches = gitlib.branches()
112
- gitlib.has_a_remote? and
113
- my_branches.include?(config.master_branch) and
114
- my_branches.current.name != config.master_branch and
115
- !keep_local_integration_branch? and
116
- my_branches[config.integration_branch].contains_all_of(config.master_branch)
112
+ has_a_remote = gitlib.has_a_remote?
113
+ my_branches = gitlib.branches
114
+ includes_master_branch = my_branches.include?(config.master_branch)
115
+ current_branch_is_not_master = my_branches.current.name != config.master_branch
116
+ do_not_keep_integration_branch = !keep_local_integration_branch?
117
+ integration_branch_contains_all_of_master = my_branches[config.integration_branch].contains_all_of(config.master_branch)
118
+
119
+ return (has_a_remote and
120
+ includes_master_branch and
121
+ current_branch_is_not_master and
122
+ do_not_keep_integration_branch and
123
+ integration_branch_contains_all_of_master)
117
124
  end
118
125
 
119
126
 
@@ -12,9 +12,6 @@
12
12
 
13
13
  require 'git-process/git_config'
14
14
  require 'addressable/uri'
15
- #require 'git-process/git_branches'
16
- #require 'git-process/git_status'
17
- #require 'git-process/git_process_error'
18
15
 
19
16
 
20
17
  class String
@@ -61,7 +58,7 @@ module GitProc
61
58
 
62
59
 
63
60
  # @deprecated
64
- # TODO: Remove
61
+ # @todo Remove
65
62
  def server_name
66
63
  @server_name ||= self.remote_name
67
64
  end
@@ -77,6 +74,13 @@ module GitProc
77
74
  end
78
75
 
79
76
 
77
+ #
78
+ # The name of the repository
79
+ #
80
+ # @example
81
+ # repo_name #=> "jdigger/git-process"
82
+ #
83
+ # @return [String] the name of the repository
80
84
  def repo_name
81
85
  unless @repo_name
82
86
  url = config["remote.#{name}.url"]
@@ -88,7 +92,14 @@ module GitProc
88
92
  end
89
93
 
90
94
 
91
- def name
95
+ #
96
+ # Returns the "remote name" to use. By convention the most common name is "origin".
97
+ #
98
+ # If the Git configuration "gitProcess.remoteName" is set, that will always be used. Otherwise this
99
+ # simple returns the first name it finds in the list of remotes.
100
+ #
101
+ # @return [String, nil] the remote name, or nil if there are none defined
102
+ def remote_name
92
103
  unless @remote_name
93
104
  @remote_name = config['gitProcess.remoteName']
94
105
  if @remote_name.nil? or @remote_name.empty?
@@ -97,7 +108,7 @@ module GitProc
97
108
  @remote_name = nil
98
109
  else
99
110
  @remote_name = remotes[0]
100
- raise '!@remote_name.is_a? String' unless @remote_name.is_a? String
111
+ raise "remote name is not a String: #{@remote_name.inspect}" unless @remote_name.is_a? String
101
112
  end
102
113
  end
103
114
  logger.debug { "Using remote name of '#{@remote_name}'" }
@@ -106,11 +117,25 @@ module GitProc
106
117
  end
107
118
 
108
119
 
120
+ alias :name :remote_name
121
+
122
+
123
+ #
124
+ # Takes {#remote_name} and combines it with {GitConfig#master_branch}.
125
+ #
126
+ # @example
127
+ # master_branch_name #=> origin/master
128
+ #
129
+ # @return [String] the complete remote name of the integration branch
130
+ #
109
131
  def master_branch_name
110
132
  "#{self.name}/#{config.master_branch}"
111
133
  end
112
134
 
113
135
 
136
+ alias :remote_integration_branch_name :master_branch_name
137
+
138
+
114
139
  def remote_names
115
140
  remote_str = config.gitlib.command(:remote, [:show])
116
141
  if remote_str.nil? or remote_str.empty?
@@ -136,6 +161,8 @@ module GitProc
136
161
  # @raise [URI::InvalidURIError] the retrieved URL does not have a schema
137
162
  # @raise [GitHubService::NoRemoteRepository] if could not figure out a host for the retrieved URL
138
163
  # @raise [::ArgumentError] if a server name is not provided
164
+ #
165
+ # @todo use the netrc gem
139
166
  def expanded_url(server_name = 'origin', raw_url = nil, opts = {})
140
167
  if raw_url.nil?
141
168
  raise ArgumentError.new('Need server_name') unless server_name
@@ -185,6 +212,7 @@ module GitProc
185
212
  alias :add :add_remote
186
213
 
187
214
 
215
+ # @todo use the netrc gem
188
216
  #noinspection RubyClassMethodNamingConvention
189
217
  def self.hostname_and_user_from_ssh_config(host_alias, config_file)
190
218
  if File.exists?(config_file)
@@ -10,31 +10,34 @@
10
10
  # See the License for the specific language governing permissions and
11
11
  # limitations under the License.
12
12
 
13
- require 'git-process/git_lib'
13
+ require File.dirname(__FILE__) + '/git_lib'
14
+ require File.dirname(__FILE__) + '/git_logger'
14
15
  require 'highline/import'
15
16
  require 'octokit'
17
+ require 'octokit/default'
16
18
  require 'uri'
19
+ require 'faraday'
20
+ require 'faraday/response/logger'
17
21
 
18
22
 
19
- #
20
- # Provides methods related to GitHub configuration
21
- #
22
23
  module GitHubService
23
24
 
25
+ #
26
+ # Provides methods related to GitHub configuration
27
+ #
24
28
  class Configuration
25
29
 
26
- attr_reader :git_config
27
-
28
-
29
30
  #
30
31
  # @param [GitProc::GitConfig] git_config
31
32
  # @param [Hash] opts
32
- # @option opts [String] :remote_name (#remote_name) The "remote" name to use (e.g., 'origin')
33
+ # @option opts [String] :remote_name (Configuration#remote_name) The "remote" name to use (e.g., 'origin')
33
34
  # @option opts [String] :user the username to authenticate with
34
- # @option opts [String] :password (#password) the password to authenticate with
35
+ # @option opts [String] :password (Configuration#password) the password to authenticate with
35
36
  #
36
37
  # @return [String] the OAuth token
37
38
  #
39
+ # @todo pass in {GitLib} instead of {GitConfig}
40
+ #
38
41
  def initialize(git_config, opts = {})
39
42
  @git_config = git_config
40
43
  @user = opts[:user]
@@ -43,7 +46,15 @@ module GitHubService
43
46
  end
44
47
 
45
48
 
46
- # @return [String]
49
+ # @return [GitProc::GitConfig]
50
+ def git_config
51
+ @git_config
52
+ end
53
+
54
+
55
+ # @return [String] the "remote name" (e.g., origin) for GitHub
56
+ #
57
+ # @see GitProc::GitRemote#remote_name
47
58
  def remote_name
48
59
  unless @remote_name
49
60
  @remote_name = gitlib.remote.name
@@ -53,21 +64,21 @@ module GitHubService
53
64
  end
54
65
 
55
66
 
56
- # @return [String]
67
+ # @return [String] the user name for GitHub
57
68
  def user
58
69
  @user ||= Configuration.ask_for_user(gitlib)
59
70
  end
60
71
 
61
72
 
62
- # @return [String]
73
+ # @return [String] the password for GitHub
63
74
  def password
64
75
  @password ||= Configuration.ask_for_password
65
76
  end
66
77
 
67
78
 
68
- # @return [Octokit::Client]
79
+ # @return [Octokit::Client] the client for communicating with GitHub using {Configuration#user} and {Configuration#auth_token}
69
80
  def client
70
- create_client
81
+ @client ||= create_client
71
82
  end
72
83
 
73
84
 
@@ -77,33 +88,26 @@ module GitHubService
77
88
  end
78
89
 
79
90
 
80
- # @return [Octokit::Client]
81
- def create_client(opts = {})
82
- logger.debug { "Creating GitHub client for user #{user} using token '#{auth_token}'" }
83
-
84
- base_url = opts[:base_url] || base_github_api_url_for_remote
85
-
86
- configure_octokit(:base_url => base_url)
87
-
88
- Octokit::Client.new(:login => user, :oauth_token => auth_token)
89
- end
90
-
91
-
92
91
  #
93
92
  # Configures Octokit to use the appropriate URLs for GitHub server.
94
93
  #
95
- # @param [Hash] opts the options to create a message with
96
- # @option opts [String] :base_url The base URL to use for the GitHub server
94
+ # @option opts [String] :base_url The base URL to use for the GitHub server; defaults to {#base_github_api_url_for_remote}
97
95
  #
98
96
  # @return [void]
99
97
  #
98
+ # @todo remove opts and pass in base_url directly
99
+ #
100
100
  def configure_octokit(opts = {})
101
101
  base_url = opts[:base_url] || base_github_api_url_for_remote
102
102
  Octokit.configure do |c|
103
103
  c.api_endpoint = api_endpoint(base_url)
104
104
  c.web_endpoint = web_endpoint(base_url)
105
- c.faraday_config do |f|
106
- #f.response :logger
105
+ end
106
+ if logger.level < ::GitProc::GitLogger::INFO
107
+ Octokit.middleware = Faraday::RackBuilder.new do |builder|
108
+ builder.response :logger, logger
109
+ builder.use Octokit::Response::RaiseError
110
+ builder.adapter Faraday.default_adapter
107
111
  end
108
112
  end
109
113
  end
@@ -124,7 +128,7 @@ module GitHubService
124
128
  if /github.com/ !~ base_url
125
129
  "#{base_url}/api/v3"
126
130
  else
127
- Octokit::Configuration::DEFAULT_API_ENDPOINT
131
+ Octokit::Default::API_ENDPOINT
128
132
  end
129
133
  end
130
134
 
@@ -144,7 +148,7 @@ module GitHubService
144
148
  if /github.com/ !~ base_url
145
149
  base_url
146
150
  else
147
- Octokit::Configuration::DEFAULT_WEB_ENDPOINT
151
+ Octokit::Default::WEB_ENDPOINT
148
152
  end
149
153
  end
150
154
 
@@ -167,17 +171,24 @@ module GitHubService
167
171
  # @param url [String] the URL to translate
168
172
  # @return [String] the base GitHub API URL
169
173
  #
174
+ # @example
175
+ # url_to_base_github_api_url('git@github.com:jdigger/git-process.git') #=> 'https://api.github.com'
176
+ #
177
+ # url_to_base_github_api_url('http://ghe.myco.com/jdigger/git-process.git') #=> 'http://ghe.myco.com'
178
+ #
179
+ # @todo use Octokit's improved ability to determine this
180
+ #
170
181
  def self.url_to_base_github_api_url(url)
171
182
  uri = URI.parse(url)
172
183
  host = uri.host
173
184
 
174
185
  if /github.com$/ =~ host
175
- 'https://api.github.com'
186
+ return 'https://api.github.com'
176
187
  else
177
188
  scheme = uri.scheme
178
189
  scheme = 'https' unless scheme.start_with?('http')
179
190
  host = 'unknown-host' unless host
180
- "#{scheme}://#{host}"
191
+ return "#{scheme}://#{host}"
181
192
  end
182
193
  end
183
194
 
@@ -192,11 +203,14 @@ module GitHubService
192
203
  # @option opts [String] :user the username to authenticate with
193
204
  # @option opts [String] :password (#password) the password to authenticate with
194
205
  #
206
+ # @return [Octokit::Client] the Octokit client for communicating with GitHub
207
+ #
195
208
  def create_pw_client(opts = {})
196
209
  usr = opts[:user] || user()
197
210
  pw = opts[:password] || password()
211
+ remote = opts[:remote_name] || self.remote_name
198
212
 
199
- logger.debug { "Creating GitHub client for user #{usr} using BasicAuth w/ password" }
213
+ logger.info("Authorizing #{usr} to work with #{remote}.")
200
214
 
201
215
  configure_octokit(opts)
202
216
 
@@ -224,34 +238,72 @@ module GitHubService
224
238
  #
225
239
  # Connects to GitHub to get an OAuth token.
226
240
  #
227
- # @param [Hash] opts
228
241
  # @option opts [String] :base_url The base URL to use for the GitHub server
229
- # @option opts [String] :remote_name (#remote_name) The "remote" name to use (e.g., 'origin')
242
+ # @option opts [String] :remote_name (Configuration#remote_name) The "remote" name to use (e.g., 'origin')
230
243
  # @option opts [String] :user the username to authenticate with
231
244
  # @option opts [String] :password (#password) the password to authenticate with
245
+ # @option opts [String] :two_factor (#password) the password to authenticate with
232
246
  #
233
247
  # @return [String] the OAuth token
234
248
  #
249
+ # noinspection RubyStringKeysInHashInspection
235
250
  def create_authorization(opts = {})
236
- username = opts[:user] || self.user
237
- remote = opts[:remote_name] || self.remote_name
238
- logger.info("Authorizing #{username} to work with #{remote}.")
251
+ client = opts[:client] || create_pw_client(opts)
252
+
253
+ return create_authorization_token(client, opts[:two_factor])
254
+ end
255
+
239
256
 
240
- auth = create_pw_client(opts).create_authorization(
241
- :scopes => %w(repo user gist),
242
- :note => 'Git-Process',
243
- :note_url => 'http://jdigger.github.com/git-process')
257
+ def create_authorization_token(client, two_factor)
258
+ begin
259
+ # noinspection RubyStringKeysInHashInspection
260
+ headers = two_factor ? {'X-GitHub-OTP' => two_factor} : nil
261
+ auth = client.create_authorization(
262
+ :scopes => %w(repo user gist),
263
+ :note => 'Git-Process',
264
+ :note_url => 'http://jdigger.github.com/git-process',
265
+ :headers => headers
266
+ )
267
+ rescue Octokit::OneTimePasswordRequired
268
+ return create_2f_authorization(client)
269
+ rescue Octokit::UnprocessableEntity => exp
270
+ return unprocessable_authorization(exp)
271
+ end
244
272
 
245
- config_auth_token = auth['token']
273
+ config_auth_token = auth[:token]
246
274
 
247
275
  # remember it for next time
248
276
  gitlib.config['gitProcess.github.authToken'] = config_auth_token
249
277
 
250
- config_auth_token
278
+ return config_auth_token
251
279
  end
252
280
 
253
281
 
254
- # @return [String]
282
+ #
283
+ # Connects to GitHub to get an OAuth token.
284
+ #
285
+ # @option opts [String] :base_url The base URL to use for the GitHub server
286
+ # @option opts [String] :remote_name (Configuration#remote_name) The "remote" name to use (e.g., 'origin')
287
+ # @option opts [String] :user the username to authenticate with
288
+ # @option opts [String] :password (#password) the password to authenticate with
289
+ # @option opts [String] :two_factor (#password) the password to authenticate with
290
+ #
291
+ # @return [String] the OAuth token
292
+ #
293
+ # noinspection RubyStringKeysInHashInspection
294
+ def create_2f_authorization(client)
295
+ two_factor = Configuration.ask_for_two_factor
296
+
297
+ create_authorization_token(client, two_factor)
298
+ end
299
+
300
+
301
+ def two_factor_auth(authorization_count, opts)
302
+ return create_authorization(opts.merge(:two_factor => two_factor, :authorization_count => authorization_count + 1))
303
+ end
304
+
305
+
306
+ # @return [String, nil] the OAuth token, or nil if not found
255
307
  def get_config_auth_token
256
308
  c_auth_token = gitlib.config['gitProcess.github.authToken']
257
309
  (c_auth_token.nil? or c_auth_token.empty?) ? nil : c_auth_token
@@ -266,6 +318,24 @@ module GitHubService
266
318
  private
267
319
 
268
320
 
321
+ #
322
+ # Create a client for communicating with GitHub using {Configuration#user} and {Configuration#auth_token}
323
+ #
324
+ # @return [Octokit::Client]
325
+ #
326
+ # @todo have the params passed in explicitly instead of via opts
327
+ #
328
+ def create_client(opts = {})
329
+ logger.debug { "Creating GitHub client for user #{user} using token '#{auth_token}'" }
330
+
331
+ base_url = opts[:base_url] || base_github_api_url_for_remote
332
+
333
+ configure_octokit(:base_url => base_url)
334
+
335
+ Octokit::Client.new(:access_token => auth_token)
336
+ end
337
+
338
+
269
339
  def self.ask_for_user(gitlib)
270
340
  user = gitlib.config['github.user']
271
341
  if user.nil? or user.empty?
@@ -285,6 +355,42 @@ module GitHubService
285
355
  end
286
356
  end
287
357
 
358
+
359
+ def self.ask_for_two_factor
360
+ ask("Your <%= color('GitHub', [:bold, :blue]) %> two-factor code: ") do |q|
361
+ q.validate = /^\w\w+$/
362
+ end
363
+ end
364
+
365
+
366
+ # @todo implement https://github.com/jdigger/git-process/issues/142
367
+ def ask_about_resetting_authorization
368
+ raise TokenAlreadyExists.new("The token already exists. Please check your OAuth settings for your account.")
369
+ end
370
+
371
+
372
+ #
373
+ # Tries to more gracefully handle the token already existing. See
374
+ #
375
+ # @return [String] the OAuth token
376
+ #
377
+ # @raise [TokenAlreadyExists] the token already exists
378
+ # @raise [Octokit::UnprocessableEntity] there was another problem
379
+ def unprocessable_authorization(exp)
380
+ errors = exp.errors
381
+ if not (errors.nil? or errors.empty?)
382
+ error_hash = errors[0]
383
+ if error_hash[:resource] == 'OauthAccess'
384
+ # error_hash[:code]
385
+ return ask_about_resetting_authorization
386
+ else
387
+ raise exp
388
+ end
389
+ else
390
+ raise exp
391
+ end
392
+ end
393
+
288
394
  end
289
395
 
290
396
 
@@ -295,4 +401,7 @@ module GitHubService
295
401
  class NoRemoteRepository < Error
296
402
  end
297
403
 
404
+ class TokenAlreadyExists < Error
405
+ end
406
+
298
407
  end