git-process 1.0.11 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/CHANGELOG.md +37 -9
  2. data/Gemfile +2 -2
  3. data/Gemfile.lock +17 -17
  4. data/README.md +14 -7
  5. data/bin/git-new-fb +10 -2
  6. data/bin/git-pull-request +30 -6
  7. data/bin/git-sync +5 -2
  8. data/bin/git-to-master +62 -11
  9. data/git-process.gemspec +15 -15
  10. data/lib/git-process/abstract_error_builder.rb +0 -3
  11. data/lib/git-process/changed_file_helper.rb +30 -24
  12. data/lib/git-process/git_abstract_merge_error_builder.rb +31 -11
  13. data/lib/git-process/git_branch.rb +5 -0
  14. data/lib/git-process/git_config.rb +153 -0
  15. data/lib/git-process/git_lib.rb +212 -164
  16. data/lib/git-process/git_logger.rb +84 -0
  17. data/lib/git-process/git_merge_error.rb +3 -14
  18. data/lib/git-process/git_process.rb +44 -73
  19. data/lib/git-process/git_process_options.rb +6 -6
  20. data/lib/git-process/git_rebase_error.rb +4 -13
  21. data/lib/git-process/git_remote.rb +254 -0
  22. data/lib/git-process/github_configuration.rb +298 -0
  23. data/lib/git-process/github_pull_request.rb +65 -27
  24. data/lib/git-process/new_fb.rb +14 -4
  25. data/lib/git-process/parked_changes_error.rb +1 -1
  26. data/lib/git-process/pull_request.rb +100 -13
  27. data/lib/git-process/pull_request_error.rb +25 -0
  28. data/lib/git-process/rebase_to_master.rb +47 -27
  29. data/lib/git-process/sync.rb +48 -33
  30. data/lib/git-process/uncommitted_changes_error.rb +1 -1
  31. data/lib/git-process/version.rb +2 -2
  32. data/spec/GitRepoHelper.rb +48 -25
  33. data/spec/changed_file_helper_spec.rb +39 -58
  34. data/spec/git_abstract_merge_error_builder_spec.rb +42 -33
  35. data/spec/git_branch_spec.rb +30 -30
  36. data/spec/git_config_spec.rb +45 -0
  37. data/spec/git_lib_spec.rb +103 -122
  38. data/spec/git_logger_spec.rb +66 -0
  39. data/spec/git_process_spec.rb +81 -81
  40. data/spec/git_remote_spec.rb +188 -0
  41. data/spec/git_status_spec.rb +36 -36
  42. data/spec/github_configuration_spec.rb +152 -0
  43. data/spec/github_pull_request_spec.rb +39 -35
  44. data/spec/github_test_helper.rb +49 -0
  45. data/spec/new_fb_spec.rb +65 -24
  46. data/spec/pull_request_helper.rb +94 -0
  47. data/spec/pull_request_spec.rb +128 -0
  48. data/spec/rebase_to_master_spec.rb +241 -145
  49. data/spec/spec_helper.rb +20 -0
  50. data/spec/sync_spec.rb +115 -109
  51. metadata +34 -20
  52. data/lib/git-process/github_client.rb +0 -83
  53. data/lib/git-process/github_service.rb +0 -174
  54. data/spec/github_service_spec.rb +0 -211
@@ -0,0 +1,298 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ require 'git-process/git_lib'
14
+ require 'highline/import'
15
+ require 'octokit'
16
+ require 'uri'
17
+
18
+
19
+ #
20
+ # Provides methods related to GitHub configuration
21
+ #
22
+ module GitHubService
23
+
24
+ class Configuration
25
+
26
+ attr_reader :git_config
27
+
28
+
29
+ #
30
+ # @param [GitProc::GitConfig] git_config
31
+ # @param [Hash] opts
32
+ # @option opts [String] :remote_name (#remote_name) The "remote" name to use (e.g., 'origin')
33
+ # @option opts [String] :user the username to authenticate with
34
+ # @option opts [String] :password (#password) the password to authenticate with
35
+ #
36
+ # @return [String] the OAuth token
37
+ #
38
+ def initialize(git_config, opts = {})
39
+ @git_config = git_config
40
+ @user = opts[:user]
41
+ @password = opts[:password]
42
+ @remote_name = opts[:remote_name]
43
+ end
44
+
45
+
46
+ # @return [String]
47
+ def remote_name
48
+ unless @remote_name
49
+ @remote_name = gitlib.remote.name
50
+ raise NoRemoteRepository.new('No remote repository is defined') unless @remote_name
51
+ end
52
+ @remote_name
53
+ end
54
+
55
+
56
+ # @return [String]
57
+ def user
58
+ @user ||= Configuration.ask_for_user(gitlib)
59
+ end
60
+
61
+
62
+ # @return [String]
63
+ def password
64
+ @password ||= Configuration.ask_for_password
65
+ end
66
+
67
+
68
+ # @return [Octokit::Client]
69
+ def client
70
+ create_client
71
+ end
72
+
73
+
74
+ # @return [GitProc::GitLib]
75
+ def gitlib
76
+ @git_config.gitlib
77
+ end
78
+
79
+
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
+ #
93
+ # Configures Octokit to use the appropriate URLs for GitHub server.
94
+ #
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
97
+ #
98
+ # @return [void]
99
+ #
100
+ def configure_octokit(opts = {})
101
+ base_url = opts[:base_url] || base_github_api_url_for_remote
102
+ Octokit.configure do |c|
103
+ c.api_endpoint = api_endpoint(base_url)
104
+ c.web_endpoint = web_endpoint(base_url)
105
+ c.faraday_config do |f|
106
+ #f.response :logger
107
+ end
108
+ end
109
+ end
110
+
111
+
112
+ #
113
+ # Determines the URL used for using the GitHub REST interface based
114
+ # on a "base" URL.
115
+ #
116
+ # If the "base_url" is not provided, then it assumes that this object
117
+ # has a "remote_name" property that it can ask.
118
+ #
119
+ # @param [String] base_url the base GitHub URL
120
+ # @return [String] the GitHub REST API URL
121
+ #
122
+ def api_endpoint(base_url = nil)
123
+ base_url ||= base_github_api_url_for_remote
124
+ if /github.com/ !~ base_url
125
+ "#{base_url}/api/v3"
126
+ else
127
+ Octokit::Configuration::DEFAULT_API_ENDPOINT
128
+ end
129
+ end
130
+
131
+
132
+ #
133
+ # Determines the URL used for using the GitHub web interface based
134
+ # on a "base" URL.
135
+ #
136
+ # If the "base_url" is not provided, then it assumes that this object
137
+ # has a "remote_name" property that it can ask.
138
+ #
139
+ # @param [String] base_url the base GitHub URL
140
+ # @return [String] the GitHub web URL
141
+ #
142
+ def web_endpoint(base_url = nil)
143
+ base_url ||= base_github_api_url_for_remote
144
+ if /github.com/ !~ base_url
145
+ base_url
146
+ else
147
+ Octokit::Configuration::DEFAULT_WEB_ENDPOINT
148
+ end
149
+ end
150
+
151
+
152
+ #
153
+ # Determines the base URL for GitHub API calls.
154
+ #
155
+ # @return [String] the base GitHub API URL
156
+ #
157
+ def base_github_api_url_for_remote
158
+ url = gitlib.remote.expanded_url(remote_name)
159
+ Configuration.url_to_base_github_api_url(url)
160
+ end
161
+
162
+
163
+ #
164
+ # Translate any "git known" URL to the HTTP(S) URL needed for
165
+ # GitHub API calls.
166
+ #
167
+ # @param url [String] the URL to translate
168
+ # @return [String] the base GitHub API URL
169
+ #
170
+ def self.url_to_base_github_api_url(url)
171
+ uri = URI.parse(url)
172
+ host = uri.host
173
+
174
+ if /github.com$/ =~ host
175
+ 'https://api.github.com'
176
+ else
177
+ scheme = uri.scheme
178
+ scheme = 'https' unless scheme.start_with?('http')
179
+ host = 'unknown-host' unless host
180
+ "#{scheme}://#{host}"
181
+ end
182
+ end
183
+
184
+
185
+ #
186
+ # Create a GitHub client using username and password specifically.
187
+ # Meant to be used to get an OAuth token for "regular" client calls.
188
+ #
189
+ # @param [Hash] opts the options to create a message with
190
+ # @option opts [String] :base_url The base URL to use for the GitHub server
191
+ # @option opts [String] :remote_name (#remote_name) The "remote" name to use (e.g., 'origin')
192
+ # @option opts [String] :user the username to authenticate with
193
+ # @option opts [String] :password (#password) the password to authenticate with
194
+ #
195
+ def create_pw_client(opts = {})
196
+ usr = opts[:user] || user()
197
+ pw = opts[:password] || password()
198
+
199
+ logger.debug { "Creating GitHub client for user #{usr} using BasicAuth w/ password" }
200
+
201
+ configure_octokit(opts)
202
+
203
+ Octokit::Client.new(:login => usr, :password => pw)
204
+ end
205
+
206
+
207
+ #
208
+ # Returns to OAuth token. If it's in .git/config, returns that.
209
+ # Otherwise it connects to GitHub to get the authorization token.
210
+ #
211
+ # @param [Hash] opts
212
+ # @option opts [String] :base_url The base URL to use for the GitHub server
213
+ # @option opts [String] :remote_name (#remote_name) The "remote" name to use (e.g., 'origin')
214
+ # @option opts [String] :user the username to authenticate with
215
+ # @option opts [String] :password (#password) the password to authenticate with
216
+ #
217
+ # @return [String]
218
+ #
219
+ def auth_token(opts = {})
220
+ get_config_auth_token() || create_authorization(opts)
221
+ end
222
+
223
+
224
+ #
225
+ # Connects to GitHub to get an OAuth token.
226
+ #
227
+ # @param [Hash] opts
228
+ # @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')
230
+ # @option opts [String] :user the username to authenticate with
231
+ # @option opts [String] :password (#password) the password to authenticate with
232
+ #
233
+ # @return [String] the OAuth token
234
+ #
235
+ 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}.")
239
+
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')
244
+
245
+ config_auth_token = auth['token']
246
+
247
+ # remember it for next time
248
+ gitlib.config['gitProcess.github.authToken'] = config_auth_token
249
+
250
+ config_auth_token
251
+ end
252
+
253
+
254
+ # @return [String]
255
+ def get_config_auth_token
256
+ c_auth_token = gitlib.config['gitProcess.github.authToken']
257
+ (c_auth_token.nil? or c_auth_token.empty?) ? nil : c_auth_token
258
+ end
259
+
260
+
261
+ def logger
262
+ gitlib.logger
263
+ end
264
+
265
+
266
+ private
267
+
268
+
269
+ def self.ask_for_user(gitlib)
270
+ user = gitlib.config['github.user']
271
+ if user.nil? or user.empty?
272
+ user = ask("Your <%= color('GitHub', [:bold, :blue]) %> username: ") do |q|
273
+ q.validate = /^\w\w+$/
274
+ end
275
+ gitlib.config['github.user'] = user
276
+ end
277
+ user
278
+ end
279
+
280
+
281
+ def self.ask_for_password
282
+ ask("Your <%= color('GitHub', [:bold, :blue]) %> password: ") do |q|
283
+ q.validate = /^\S\S+$/
284
+ q.echo = 'x'
285
+ end
286
+ end
287
+
288
+ end
289
+
290
+
291
+ class Error < ::StandardError
292
+ end
293
+
294
+
295
+ class NoRemoteRepository < Error
296
+ end
297
+
298
+ end
@@ -10,7 +10,7 @@
10
10
  # See the License for the specific language governing permissions and
11
11
  # limitations under the License.
12
12
 
13
- require 'git-process/github_service'
13
+ require 'git-process/github_configuration'
14
14
  require 'octokit'
15
15
  require 'octokit/repository'
16
16
 
@@ -18,21 +18,24 @@ require 'octokit/repository'
18
18
  module GitHub
19
19
 
20
20
  class PullRequest
21
- include GitHubService
21
+ attr_reader :gitlib, :repo, :remote_name, :client, :configuration
22
22
 
23
- attr_reader :lib, :repo
24
23
 
25
-
26
- def initialize(lib, repo, opts = {})
27
- @lib = lib
24
+ def initialize(lib, remote_name, repo, opts = {})
25
+ @gitlib = lib
28
26
  @repo = repo
29
- @user = opts[:user]
30
- @password = opts[:password]
27
+ @remote_name = remote_name
28
+ @configuration = GitHubService::Configuration.new(gitlib.config, :user => opts[:user], :password => opts[:password])
31
29
  end
32
30
 
33
31
 
34
- def pull_requests
35
- @pull_requests ||= client.pull_requests(repo)
32
+ def client
33
+ @client ||= @configuration.create_client
34
+ end
35
+
36
+
37
+ def pull_requests(state = 'open', opts = {})
38
+ @pull_requests ||= client.pull_requests(repo, state, opts)
36
39
  end
37
40
 
38
41
 
@@ -48,30 +51,62 @@ module GitHub
48
51
  end
49
52
 
50
53
 
51
- def find_pull_request(base, head)
52
- json = pull_requests
53
- json.find { |p| p[:head][:ref] == head and p[:base][:ref] == base }
54
+ def logger
55
+ @gitlib.logger
54
56
  end
55
57
 
56
58
 
57
- def close(*args)
58
- pull_number = nil
59
+ def pull_request(pr_number)
60
+ client.pull_request(repo, pr_number)
61
+ end
59
62
 
60
- if args.size == 2
61
- base = args[0]
62
- head = args[1]
63
- logger.info { "Closing a pull request asking for '#{head}' to be merged into '#{base}' on #{repo}." }
64
63
 
65
- json = pull_requests
66
- pull = json.find { |p| p[:head][:ref] == head and p[:base][:ref] == base }
64
+ #
65
+ # Find the pull request (PR) that matches the 'head' and 'base'.
66
+ #
67
+ # @param [String] base what the PR is merging into
68
+ # @param [String] head the branch of the PR
69
+ #
70
+ # @return [Hash]
71
+ # @raise [NotFoundError] if the pull request does not exist
72
+ #
73
+ def get_pull_request(base, head)
74
+ find_pull_request(base, head, true)
75
+ end
67
76
 
68
- raise NotFoundError.new(base, head, repo, json) if pull.nil?
69
77
 
70
- pull_number = pull[:number]
71
- elsif args.size == 1
72
- pull_number = args[0]
73
- logger.info { "Closing a pull request \##{pull_number} on #{repo}." }
74
- end
78
+ #
79
+ # Find the pull request (PR) that matches the 'head' and 'base'.
80
+ #
81
+ # @param [String] base what the PR is merging into
82
+ # @param [String] head the branch of the PR
83
+ # @param [boolean] error_if_missing should this error-out if the PR is not found?
84
+ #
85
+ # @return [Hash, nil]
86
+ # @raise [NotFoundError] if the pull request does not exist and 'error_if_missing' is true
87
+ #
88
+ def find_pull_request(base, head, error_if_missing = false)
89
+ logger.info { "Looking for a pull request asking for '#{head}' to be merged into '#{base}' on #{repo}." }
90
+
91
+ json = pull_requests
92
+ pr = json.find { |p| p[:head][:ref] == head and p[:base][:ref] == base }
93
+
94
+ raise NotFoundError.new(base, head, repo, json) if error_if_missing && pr.nil?
95
+
96
+ pr
97
+ end
98
+
99
+
100
+ def close(*args)
101
+ pull_number = if args.size == 2
102
+ get_pull_request(args[0], args[1])[:number]
103
+ elsif args.size == 1
104
+ args[0]
105
+ else
106
+ raise ArgumentError.new('close(..) needs 1 or 2 arguments')
107
+ end
108
+
109
+ logger.info { "Closing a pull request \##{pull_number} on #{repo}." }
75
110
 
76
111
  client.patch("repos/#{Octokit::Repository.new(repo)}/pulls/#{pull_number}", {:state => 'closed'})
77
112
  end
@@ -104,6 +139,9 @@ module GitHub
104
139
 
105
140
  end
106
141
 
142
+ private
143
+
144
+
107
145
  end
108
146
 
109
147
  end
@@ -23,15 +23,25 @@ module GitProc
23
23
 
24
24
 
25
25
  def runner
26
- mybranches = branches()
26
+ mybranches = gitlib.branches()
27
27
  on_parking = (mybranches.parking == mybranches.current)
28
28
 
29
29
  if on_parking
30
- new_branch = checkout(@branch_name, :new_branch => '_parking_')
31
- mybranches.parking.delete!
30
+ base_branch = if mybranches[config.integration_branch].contains_all_of(mybranches.parking.name)
31
+ config.integration_branch
32
+ else
33
+ '_parking_'
34
+ end
35
+
36
+ logger.info { "Creating #{@branch_name} off of #{base_branch}" }
37
+ new_branch = gitlib.checkout(@branch_name, :new_branch => base_branch)
38
+
39
+ branches = gitlib.branches()
40
+ branches[@branch_name].upstream(config.integration_branch)
41
+ branches.parking.delete!
32
42
  new_branch
33
43
  else
34
- checkout(@branch_name, :new_branch => integration_branch)
44
+ gitlib.checkout(@branch_name, :new_branch => config.integration_branch)
35
45
  end
36
46
  end
37
47