git-process 1.0.11 → 1.1.0

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 (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