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.
- data/CHANGELOG.md +37 -9
- data/Gemfile +2 -2
- data/Gemfile.lock +17 -17
- data/README.md +14 -7
- data/bin/git-new-fb +10 -2
- data/bin/git-pull-request +30 -6
- data/bin/git-sync +5 -2
- data/bin/git-to-master +62 -11
- data/git-process.gemspec +15 -15
- data/lib/git-process/abstract_error_builder.rb +0 -3
- data/lib/git-process/changed_file_helper.rb +30 -24
- data/lib/git-process/git_abstract_merge_error_builder.rb +31 -11
- data/lib/git-process/git_branch.rb +5 -0
- data/lib/git-process/git_config.rb +153 -0
- data/lib/git-process/git_lib.rb +212 -164
- data/lib/git-process/git_logger.rb +84 -0
- data/lib/git-process/git_merge_error.rb +3 -14
- data/lib/git-process/git_process.rb +44 -73
- data/lib/git-process/git_process_options.rb +6 -6
- data/lib/git-process/git_rebase_error.rb +4 -13
- data/lib/git-process/git_remote.rb +254 -0
- data/lib/git-process/github_configuration.rb +298 -0
- data/lib/git-process/github_pull_request.rb +65 -27
- data/lib/git-process/new_fb.rb +14 -4
- data/lib/git-process/parked_changes_error.rb +1 -1
- data/lib/git-process/pull_request.rb +100 -13
- data/lib/git-process/pull_request_error.rb +25 -0
- data/lib/git-process/rebase_to_master.rb +47 -27
- data/lib/git-process/sync.rb +48 -33
- data/lib/git-process/uncommitted_changes_error.rb +1 -1
- data/lib/git-process/version.rb +2 -2
- data/spec/GitRepoHelper.rb +48 -25
- data/spec/changed_file_helper_spec.rb +39 -58
- data/spec/git_abstract_merge_error_builder_spec.rb +42 -33
- data/spec/git_branch_spec.rb +30 -30
- data/spec/git_config_spec.rb +45 -0
- data/spec/git_lib_spec.rb +103 -122
- data/spec/git_logger_spec.rb +66 -0
- data/spec/git_process_spec.rb +81 -81
- data/spec/git_remote_spec.rb +188 -0
- data/spec/git_status_spec.rb +36 -36
- data/spec/github_configuration_spec.rb +152 -0
- data/spec/github_pull_request_spec.rb +39 -35
- data/spec/github_test_helper.rb +49 -0
- data/spec/new_fb_spec.rb +65 -24
- data/spec/pull_request_helper.rb +94 -0
- data/spec/pull_request_spec.rb +128 -0
- data/spec/rebase_to_master_spec.rb +241 -145
- data/spec/spec_helper.rb +20 -0
- data/spec/sync_spec.rb +115 -109
- metadata +34 -20
- data/lib/git-process/github_client.rb +0 -83
- data/lib/git-process/github_service.rb +0 -174
- 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/
|
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
|
-
|
21
|
+
attr_reader :gitlib, :repo, :remote_name, :client, :configuration
|
22
22
|
|
23
|
-
attr_reader :lib, :repo
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
@lib = lib
|
24
|
+
def initialize(lib, remote_name, repo, opts = {})
|
25
|
+
@gitlib = lib
|
28
26
|
@repo = repo
|
29
|
-
@
|
30
|
-
@
|
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
|
35
|
-
@
|
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
|
52
|
-
|
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
|
58
|
-
|
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
|
-
|
66
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
data/lib/git-process/new_fb.rb
CHANGED
@@ -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
|
-
|
31
|
-
|
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
|
|