hub 1.7.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of hub might be problematic. Click here for more details.
- data/README.md +0 -1
- data/lib/hub.rb +1 -0
- data/lib/hub/commands.rb +113 -92
- data/lib/hub/context.rb +155 -20
- data/lib/hub/json.rb +76 -0
- data/lib/hub/version.rb +1 -1
- data/man/hub.1 +43 -3
- data/man/hub.1.html +24 -3
- data/man/hub.1.ronn +20 -2
- data/test/hub_test.rb +213 -42
- metadata +4 -3
data/README.md
CHANGED
@@ -365,7 +365,6 @@ You will need the following libraries for development:
|
|
365
365
|
|
366
366
|
* [ronn](https://github.com/rtomayko/ronn) (building man pages)
|
367
367
|
* [webmock](https://github.com/bblimke/webmock)
|
368
|
-
* [json](http://flori.github.com/json/) (ruby 1.8 only)
|
369
368
|
|
370
369
|
Meta
|
371
370
|
----
|
data/lib/hub.rb
CHANGED
data/lib/hub/commands.rb
CHANGED
@@ -34,13 +34,9 @@ module Hub
|
|
34
34
|
# provides git interrogation methods
|
35
35
|
extend Context
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
API_PULL = 'http://github.com/api/v2/json/pulls/%s'
|
41
|
-
API_PULLREQUEST = 'https://github.com/api/v2/yaml/pulls/%s/%s'
|
42
|
-
|
43
|
-
NAME_WITH_OWNER_RE = /^([\w-]+)(?:\/([\w-]+))?$/
|
37
|
+
NAME_RE = /[\w.-]+/
|
38
|
+
OWNER_RE = /[a-zA-Z0-9-]+/
|
39
|
+
NAME_WITH_OWNER_RE = /^(?:#{NAME_RE}|#{OWNER_RE}\/#{NAME_RE})$/
|
44
40
|
|
45
41
|
def run(args)
|
46
42
|
slurp_global_flags(args)
|
@@ -97,11 +93,11 @@ module Hub
|
|
97
93
|
head_project, options[:head] = from_github_ref.call(head, head_project)
|
98
94
|
when '-i'
|
99
95
|
options[:issue] = args.shift
|
100
|
-
when %r{^https?://github.com/([^/]+/[^/]+)/issues/(\d+)}
|
101
|
-
options[:issue] = $2
|
102
|
-
base_project = github_project($1)
|
103
96
|
else
|
104
|
-
if
|
97
|
+
if url = resolve_github_url(arg) and url.project_path =~ /^issues\/(\d+)/
|
98
|
+
options[:issue] = $1
|
99
|
+
base_project = url.project
|
100
|
+
elsif !options[:title] then options[:title] = arg
|
105
101
|
else
|
106
102
|
abort "invalid argument: #{arg}"
|
107
103
|
end
|
@@ -120,8 +116,10 @@ module Hub
|
|
120
116
|
end
|
121
117
|
options[:head] ||= (tracked_branch || current_branch).short_name
|
122
118
|
|
123
|
-
|
124
|
-
|
119
|
+
# when no tracking, assume remote branch is published under active user's fork
|
120
|
+
user = github_user(true, head_project.host)
|
121
|
+
if head_project.owner != user and !tracked_branch and !explicit_owner
|
122
|
+
head_project = head_project.owned_by(user)
|
125
123
|
end
|
126
124
|
|
127
125
|
remote_branch = "#{head_project.remote}/#{options[:head]}"
|
@@ -184,8 +182,12 @@ module Hub
|
|
184
182
|
# $ hub clone rtomayko/tilt
|
185
183
|
# $ hub clone tilt
|
186
184
|
if arg =~ NAME_WITH_OWNER_RE
|
187
|
-
|
188
|
-
|
185
|
+
# FIXME: this logic shouldn't be duplicated here!
|
186
|
+
name, owner = arg, nil
|
187
|
+
owner, name = name.split('/', 2) if name.index('/')
|
188
|
+
host = ENV['GITHUB_HOST']
|
189
|
+
project = Context::GithubProject.new(nil, owner || github_user(true, host), name, host || 'github.com')
|
190
|
+
ssh ||= args[0] != 'submodule' && project.owner == github_user(false, host) || host
|
189
191
|
args[idx] = project.git_url(:private => ssh, :https => https_protocol?)
|
190
192
|
end
|
191
193
|
break
|
@@ -229,11 +231,13 @@ module Hub
|
|
229
231
|
# $ hub remote add origin
|
230
232
|
# > git remote add origin git://github.com/YOUR_LOGIN/THIS_REPO.git
|
231
233
|
def remote(args)
|
232
|
-
if %w[add set-url].include?(args[1])
|
233
|
-
|
234
|
-
|
235
|
-
|
234
|
+
if %w[add set-url].include?(args[1])
|
235
|
+
name = args.last
|
236
|
+
if name =~ /^(#{OWNER_RE})$/ || name =~ /^(#{OWNER_RE})\/(#{NAME_RE})$/
|
237
|
+
user, repo = $1, $2 || repo_name
|
238
|
+
end
|
236
239
|
end
|
240
|
+
return unless user # do not touch arguments
|
237
241
|
|
238
242
|
ssh = args.delete('-p')
|
239
243
|
|
@@ -283,14 +287,16 @@ module Hub
|
|
283
287
|
names = []
|
284
288
|
end
|
285
289
|
|
286
|
-
names.
|
287
|
-
name =~ /\W/ or remotes.include?(name) or
|
288
|
-
|
289
|
-
|
290
|
+
projects = names.map { |name|
|
291
|
+
unless name =~ /\W/ or remotes.include?(name) or remotes_group(name)
|
292
|
+
project = github_project(nil, name)
|
293
|
+
project if repo_exists?(project)
|
294
|
+
end
|
295
|
+
}.compact
|
290
296
|
|
291
|
-
if
|
292
|
-
|
293
|
-
args.before ['remote', 'add',
|
297
|
+
if projects.any?
|
298
|
+
projects.each do |project|
|
299
|
+
args.before ['remote', 'add', project.owner, project.git_url(:https => https_protocol?)]
|
294
300
|
end
|
295
301
|
end
|
296
302
|
end
|
@@ -299,20 +305,23 @@ module Hub
|
|
299
305
|
# > git remote add -f -t feature git://github:com/mislav/hub.git
|
300
306
|
# > git checkout -b mislav-feature mislav/feature
|
301
307
|
def checkout(args)
|
302
|
-
if (2..3) === args.length and args[1] =~
|
303
|
-
|
308
|
+
if (2..3) === args.length and url = resolve_github_url(args[1]) and url.project_path =~ /^pull\/(\d+)/
|
309
|
+
pull_id = $1
|
304
310
|
|
305
311
|
load_net_http
|
306
|
-
|
312
|
+
response = http_request(url.project.api_pullrequest_url(pull_id, 'json'))
|
313
|
+
pull_data = JSON.parse(response.body)['pull']
|
307
314
|
|
308
|
-
user, branch =
|
315
|
+
user, branch = pull_data['head']['label'].split(':', 2)
|
309
316
|
new_branch_name = args[2] || "#{user}-#{branch}"
|
310
317
|
|
311
318
|
if remotes.include? user
|
312
319
|
args.before ['remote', 'set-branches', '--add', user, branch]
|
313
320
|
args.before ['fetch', user, "+refs/heads/#{branch}:refs/remotes/#{user}/#{branch}"]
|
314
321
|
else
|
315
|
-
|
322
|
+
url = github_project(url.project_name, user).git_url(:private => pull_data['head']['repository']['private'],
|
323
|
+
:https => https_protocol?)
|
324
|
+
args.before ['remote', 'add', '-f', '-t', branch, user, url]
|
316
325
|
end
|
317
326
|
args[1..-1] = ['-b', new_branch_name, "#{user}/#{branch}"]
|
318
327
|
end
|
@@ -331,25 +340,22 @@ module Hub
|
|
331
340
|
# > git cherry-pick SHA
|
332
341
|
def cherry_pick(args)
|
333
342
|
unless args.include?('-m') or args.include?('--mainline')
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
else
|
342
|
-
user = nil
|
343
|
+
ref = args.words.last
|
344
|
+
if url = resolve_github_url(ref) and url.project_path =~ /^commit\/([a-f0-9]{7,40})/
|
345
|
+
sha = $1
|
346
|
+
project = url.project
|
347
|
+
elsif ref =~ /^(#{OWNER_RE})@([a-f0-9]{7,40})$/
|
348
|
+
owner, sha = $1, $2
|
349
|
+
project = local_repo.main_project.owned_by(owner)
|
343
350
|
end
|
344
351
|
|
345
|
-
if
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
args.before ['fetch', user]
|
352
|
+
if project
|
353
|
+
args[args.index(ref)] = sha
|
354
|
+
|
355
|
+
if remote = project.remote and remotes.include? remote
|
356
|
+
args.before ['fetch', remote]
|
351
357
|
else
|
352
|
-
args.before ['remote', 'add', '-f',
|
358
|
+
args.before ['remote', 'add', '-f', project.owner, project.git_url(:https => https_protocol?)]
|
353
359
|
end
|
354
360
|
end
|
355
361
|
end
|
@@ -366,7 +372,7 @@ module Hub
|
|
366
372
|
url = url.sub(%r{(/pull/\d+)/\w*$}, '\1') unless gist
|
367
373
|
ext = gist ? '.txt' : '.patch'
|
368
374
|
url += ext unless File.extname(url) == ext
|
369
|
-
patch_file = File.join(ENV['TMPDIR'], "#{gist ? 'gist-' : ''}#{File.basename(url)}")
|
375
|
+
patch_file = File.join(ENV['TMPDIR'] || '/tmp', "#{gist ? 'gist-' : ''}#{File.basename(url)}")
|
370
376
|
args.before 'curl', ['-#LA', "hub #{Hub::Version}", url, '-o', patch_file]
|
371
377
|
args[idx] = patch_file
|
372
378
|
end
|
@@ -382,7 +388,11 @@ module Hub
|
|
382
388
|
# > git remote add origin git@github.com:USER/REPO.git
|
383
389
|
def init(args)
|
384
390
|
if args.delete('-g')
|
385
|
-
|
391
|
+
# can't use default_host because there is no local_repo yet
|
392
|
+
# FIXME: this shouldn't be here!
|
393
|
+
host = ENV['GITHUB_HOST']
|
394
|
+
project = Context::GithubProject.new(nil, github_user(true, host), File.basename(current_dir), host || 'github.com')
|
395
|
+
url = project.git_url(:private => true, :https => https_protocol?)
|
386
396
|
args.after ['remote', 'add', 'origin', url]
|
387
397
|
end
|
388
398
|
end
|
@@ -391,21 +401,22 @@ module Hub
|
|
391
401
|
# ... hardcore forking action ...
|
392
402
|
# > git remote add -f YOUR_USER git@github.com:YOUR_USER/CURRENT_REPO.git
|
393
403
|
def fork(args)
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
404
|
+
unless project = local_repo.main_project
|
405
|
+
abort "Error: repository under 'origin' remote is not a GitHub project"
|
406
|
+
end
|
407
|
+
forked_project = project.owned_by(github_user(true, project.host))
|
408
|
+
if repo_exists?(forked_project)
|
409
|
+
warn "#{forked_project.name_with_owner} already exists on #{forked_project.host}"
|
410
|
+
else
|
411
|
+
fork_repo(project) unless args.noop?
|
412
|
+
end
|
401
413
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
end
|
414
|
+
if args.include?('--no-remote')
|
415
|
+
exit
|
416
|
+
else
|
417
|
+
url = forked_project.git_url(:private => true, :https => https_protocol?)
|
418
|
+
args.replace %W"remote add -f #{forked_project.owner} #{url}"
|
419
|
+
args.after 'echo', ['new remote:', forked_project.owner]
|
409
420
|
end
|
410
421
|
rescue HTTPExceptions
|
411
422
|
display_http_exception("creating fork", $!.response)
|
@@ -440,17 +451,17 @@ module Hub
|
|
440
451
|
end
|
441
452
|
end
|
442
453
|
new_repo_name ||= repo_name
|
443
|
-
|
454
|
+
new_project = github_project(new_repo_name, owner)
|
444
455
|
|
445
|
-
if repo_exists?(
|
446
|
-
warn "#{
|
456
|
+
if repo_exists?(new_project)
|
457
|
+
warn "#{new_project.name_with_owner} already exists on #{new_project.host}"
|
447
458
|
action = "set remote origin"
|
448
459
|
else
|
449
460
|
action = "created repository"
|
450
|
-
create_repo(
|
461
|
+
create_repo(new_project, options) unless args.noop?
|
451
462
|
end
|
452
463
|
|
453
|
-
url = git_url(
|
464
|
+
url = new_project.git_url(:private => true, :https => https_protocol?)
|
454
465
|
|
455
466
|
if remotes.first != 'origin'
|
456
467
|
args.replace %W"remote add -f origin #{url}"
|
@@ -458,7 +469,7 @@ module Hub
|
|
458
469
|
args.replace %W"remote -v"
|
459
470
|
end
|
460
471
|
|
461
|
-
args.after 'echo', ["#{action}:",
|
472
|
+
args.after 'echo', ["#{action}:", new_project.name_with_owner]
|
462
473
|
end
|
463
474
|
rescue HTTPExceptions
|
464
475
|
display_http_exception("creating repository", $!.response)
|
@@ -831,32 +842,32 @@ help
|
|
831
842
|
end
|
832
843
|
|
833
844
|
# Determines whether a user has a fork of the current repo on GitHub.
|
834
|
-
def repo_exists?(
|
845
|
+
def repo_exists?(project)
|
835
846
|
load_net_http
|
836
|
-
|
837
|
-
Net::HTTPSuccess === Net::HTTP.get_response(URI(url))
|
847
|
+
Net::HTTPSuccess === http_request(project.api_show_url('yaml'))
|
838
848
|
end
|
839
849
|
|
840
850
|
# Forks the current repo using the GitHub API.
|
841
851
|
#
|
842
852
|
# Returns nothing.
|
843
|
-
def fork_repo
|
853
|
+
def fork_repo(project)
|
844
854
|
load_net_http
|
845
|
-
response = http_post
|
855
|
+
response = http_post project.api_fork_url('yaml')
|
846
856
|
response.error! unless Net::HTTPSuccess === response
|
847
857
|
end
|
848
858
|
|
849
859
|
# Creates a new repo using the GitHub API.
|
850
860
|
#
|
851
861
|
# Returns nothing.
|
852
|
-
def create_repo(
|
853
|
-
|
862
|
+
def create_repo(project, options = {})
|
863
|
+
is_org = project.owner != github_user(true, project.host)
|
864
|
+
params = {'name' => is_org ? project.name_with_owner : project.name}
|
854
865
|
params['public'] = '0' if options[:private]
|
855
866
|
params['description'] = options[:description] if options[:description]
|
856
867
|
params['homepage'] = options[:homepage] if options[:homepage]
|
857
868
|
|
858
869
|
load_net_http
|
859
|
-
response = http_post(
|
870
|
+
response = http_post(project.api_create_url('yaml'), params)
|
860
871
|
response.error! unless Net::HTTPSuccess === response
|
861
872
|
end
|
862
873
|
|
@@ -872,15 +883,9 @@ help
|
|
872
883
|
params['pull[body]'] = options[:body] if options[:body]
|
873
884
|
|
874
885
|
load_net_http
|
875
|
-
response = http_post(
|
886
|
+
response = http_post(project.api_create_pullrequest_url('json'), params)
|
876
887
|
response.error! unless Net::HTTPSuccess === response
|
877
|
-
|
878
|
-
if response['Content-type'].to_s.include? 'application/json'
|
879
|
-
{ "html_url" => response.body.match(/"html_url":\s*"(.+?)"/)[1] }
|
880
|
-
else
|
881
|
-
require 'yaml'
|
882
|
-
YAML.load(response.body)['pull']
|
883
|
-
end
|
888
|
+
JSON.parse(response.body)['pull']
|
884
889
|
end
|
885
890
|
|
886
891
|
def pullrequest_editmsg(changes)
|
@@ -925,11 +930,12 @@ help
|
|
925
930
|
end
|
926
931
|
end
|
927
932
|
|
928
|
-
def
|
929
|
-
url = URI(url)
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
+
def http_request(url, type = :Get)
|
934
|
+
url = URI(url) unless url.respond_to? :host
|
935
|
+
user, token = github_user(type != :Get, url.host), github_token(type != :Get, url.host)
|
936
|
+
|
937
|
+
req = Net::HTTP.const_get(type).new(url.request_uri)
|
938
|
+
req.basic_auth "#{user}/token", token if user and token
|
933
939
|
|
934
940
|
port = url.port
|
935
941
|
if use_ssl = 'https' == url.scheme and not use_ssl?
|
@@ -943,7 +949,16 @@ help
|
|
943
949
|
# TODO: SSL peer verification
|
944
950
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
945
951
|
end
|
946
|
-
|
952
|
+
|
953
|
+
yield req if block_given?
|
954
|
+
http.start { http.request(req) }
|
955
|
+
end
|
956
|
+
|
957
|
+
def http_post(url, params = nil)
|
958
|
+
http_request(url, :Post) do |req|
|
959
|
+
req.set_form_data params if params
|
960
|
+
req['Content-Length'] = req.body ? req.body.length : 0
|
961
|
+
end
|
947
962
|
end
|
948
963
|
|
949
964
|
def load_net_http
|
@@ -966,7 +981,13 @@ help
|
|
966
981
|
|
967
982
|
def display_http_exception(action, response)
|
968
983
|
$stderr.puts "Error #{action}: #{response.message} (HTTP #{response.code})"
|
969
|
-
|
984
|
+
case response.code.to_i
|
985
|
+
when 401 then warn "Check your token configuration (`git config github.token`)"
|
986
|
+
when 422
|
987
|
+
if response.content_type =~ /\bjson\b/ and data = JSON.parse(response.body) and data["error"]
|
988
|
+
$stderr.puts data["error"]
|
989
|
+
end
|
990
|
+
end
|
970
991
|
end
|
971
992
|
|
972
993
|
end
|
data/lib/hub/context.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'shellwords'
|
2
2
|
require 'forwardable'
|
3
|
+
require 'uri'
|
3
4
|
|
4
5
|
module Hub
|
5
6
|
# Provides methods for inspecting the environment, such as GitHub user/token
|
@@ -83,16 +84,20 @@ module Hub
|
|
83
84
|
include GitReaderMethods
|
84
85
|
private :git_config, :git_command
|
85
86
|
|
86
|
-
def local_repo
|
87
|
+
def local_repo(fatal = true)
|
87
88
|
@local_repo ||= begin
|
88
|
-
|
89
|
+
if is_repo?
|
90
|
+
LocalRepo.new git_reader, current_dir
|
91
|
+
elsif fatal
|
92
|
+
abort "fatal: Not a git repository"
|
93
|
+
end
|
89
94
|
end
|
90
95
|
end
|
91
96
|
|
92
97
|
repo_methods = [
|
93
98
|
:current_branch, :master_branch,
|
94
99
|
:current_project, :upstream_project,
|
95
|
-
:repo_owner,
|
100
|
+
:repo_owner, :repo_host,
|
96
101
|
:remotes, :remotes_group, :origin_remote
|
97
102
|
]
|
98
103
|
def_delegator :local_repo, :name, :repo_name
|
@@ -116,6 +121,10 @@ module Hub
|
|
116
121
|
end
|
117
122
|
end
|
118
123
|
|
124
|
+
def repo_host
|
125
|
+
project = main_project and project.host
|
126
|
+
end
|
127
|
+
|
119
128
|
def main_project
|
120
129
|
remote = origin_remote and remote.project
|
121
130
|
end
|
@@ -162,9 +171,43 @@ module Hub
|
|
162
171
|
def remote_by_name(remote_name)
|
163
172
|
remotes.find {|r| r.name == remote_name }
|
164
173
|
end
|
174
|
+
|
175
|
+
def known_hosts
|
176
|
+
git_config('hub.host', :all).to_s.split("\n") + [default_host]
|
177
|
+
end
|
178
|
+
|
179
|
+
def default_host
|
180
|
+
ENV['GITHUB_HOST'] || main_host
|
181
|
+
end
|
182
|
+
|
183
|
+
def main_host
|
184
|
+
'github.com'
|
185
|
+
end
|
165
186
|
end
|
166
187
|
|
167
|
-
class GithubProject < Struct.new(:local_repo, :owner, :name)
|
188
|
+
class GithubProject < Struct.new(:local_repo, :owner, :name, :host)
|
189
|
+
def self.from_url(url, local_repo)
|
190
|
+
if local_repo.known_hosts.include? url.host
|
191
|
+
_, owner, name = url.path.split('/', 4)
|
192
|
+
GithubProject.new(local_repo, owner, name.sub(/\.git$/, ''), url.host)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def initialize(*args)
|
197
|
+
super
|
198
|
+
self.host ||= local_repo.default_host
|
199
|
+
end
|
200
|
+
|
201
|
+
def private?
|
202
|
+
local_repo and host != local_repo.main_host
|
203
|
+
end
|
204
|
+
|
205
|
+
def owned_by(new_owner)
|
206
|
+
new_project = dup
|
207
|
+
new_project.owner = new_owner
|
208
|
+
new_project
|
209
|
+
end
|
210
|
+
|
168
211
|
def name_with_owner
|
169
212
|
"#{owner}/#{name}"
|
170
213
|
end
|
@@ -187,22 +230,74 @@ module Hub
|
|
187
230
|
path = '/wiki' + path
|
188
231
|
end
|
189
232
|
end
|
190
|
-
|
233
|
+
"https://#{host}/" + project_name + path.to_s
|
191
234
|
end
|
192
235
|
|
193
236
|
def git_url(options = {})
|
194
|
-
if options[:https] then
|
195
|
-
elsif options[:private] then
|
196
|
-
else
|
237
|
+
if options[:https] then "https://#{host}/"
|
238
|
+
elsif options[:private] or private? then "git@#{host}:"
|
239
|
+
else "git://#{host}/"
|
197
240
|
end + name_with_owner + '.git'
|
198
241
|
end
|
242
|
+
|
243
|
+
def api_url(type, resource, action)
|
244
|
+
URI("https://#{host}/api/v2/#{type}/#{resource}/#{action}")
|
245
|
+
end
|
246
|
+
|
247
|
+
def api_show_url(type)
|
248
|
+
api_url(type, 'repos', "show/#{owner}/#{name}")
|
249
|
+
end
|
250
|
+
|
251
|
+
def api_fork_url(type)
|
252
|
+
api_url(type, 'repos', "fork/#{owner}/#{name}")
|
253
|
+
end
|
254
|
+
|
255
|
+
def api_create_url(type)
|
256
|
+
api_url(type, 'repos', 'create')
|
257
|
+
end
|
258
|
+
|
259
|
+
def api_pullrequest_url(id, type)
|
260
|
+
api_url(type, 'pulls', "#{owner}/#{name}/#{id}")
|
261
|
+
end
|
262
|
+
|
263
|
+
def api_create_pullrequest_url(type)
|
264
|
+
api_url(type, 'pulls', "#{owner}/#{name}")
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
class GithubURL < URI::HTTPS
|
269
|
+
extend Forwardable
|
270
|
+
|
271
|
+
attr_reader :project
|
272
|
+
def_delegator :project, :name, :project_name
|
273
|
+
def_delegator :project, :owner, :project_owner
|
274
|
+
|
275
|
+
def self.resolve(url, local_repo)
|
276
|
+
u = URI(url)
|
277
|
+
if %[http https].include? u.scheme and project = GithubProject.from_url(u, local_repo)
|
278
|
+
self.new(u.scheme, u.userinfo, u.host, u.port, u.registry,
|
279
|
+
u.path, u.opaque, u.query, u.fragment, project)
|
280
|
+
end
|
281
|
+
rescue URI::InvalidURIError
|
282
|
+
nil
|
283
|
+
end
|
284
|
+
|
285
|
+
def initialize(*args)
|
286
|
+
@project = args.pop
|
287
|
+
super(*args)
|
288
|
+
end
|
289
|
+
|
290
|
+
# segment of path after the project owner and name
|
291
|
+
def project_path
|
292
|
+
path.split('/', 4)[3]
|
293
|
+
end
|
199
294
|
end
|
200
295
|
|
201
296
|
class Branch < Struct.new(:local_repo, :name)
|
202
297
|
alias to_s name
|
203
298
|
|
204
299
|
def short_name
|
205
|
-
name.
|
300
|
+
name.sub(%r{^refs/(remotes/)?.+?/}, '')
|
206
301
|
end
|
207
302
|
|
208
303
|
def master?
|
@@ -233,13 +328,24 @@ module Hub
|
|
233
328
|
end
|
234
329
|
|
235
330
|
def project
|
236
|
-
|
237
|
-
GithubProject.
|
238
|
-
|
331
|
+
urls.each { |url|
|
332
|
+
if valid = GithubProject.from_url(url, local_repo)
|
333
|
+
return valid
|
334
|
+
end
|
335
|
+
}
|
336
|
+
nil
|
239
337
|
end
|
240
338
|
|
241
339
|
def urls
|
242
|
-
@urls ||= local_repo.git_config("remote.#{name}.url", :all).to_s.split("\n")
|
340
|
+
@urls ||= local_repo.git_config("remote.#{name}.url", :all).to_s.split("\n").map { |uri|
|
341
|
+
begin
|
342
|
+
if uri =~ %r{^[\w-]+://} then URI(uri)
|
343
|
+
elsif uri =~ %r{^([^/]+?):} then URI("ssh://#{$1}/#{$'}") # scp-like syntax
|
344
|
+
end
|
345
|
+
rescue URI::InvalidURIError
|
346
|
+
nil
|
347
|
+
end
|
348
|
+
}.compact
|
243
349
|
end
|
244
350
|
end
|
245
351
|
|
@@ -255,7 +361,14 @@ module Hub
|
|
255
361
|
owner ||= github_user
|
256
362
|
end
|
257
363
|
|
258
|
-
|
364
|
+
if local_repo(false) and main_project = local_repo.main_project
|
365
|
+
project = main_project.dup
|
366
|
+
project.owner = owner
|
367
|
+
project.name = name
|
368
|
+
project
|
369
|
+
else
|
370
|
+
GithubProject.new(local_repo, owner, name)
|
371
|
+
end
|
259
372
|
end
|
260
373
|
|
261
374
|
def git_url(owner = nil, name = nil, options = {})
|
@@ -263,23 +376,45 @@ module Hub
|
|
263
376
|
project.git_url({:https => https_protocol?}.update(options))
|
264
377
|
end
|
265
378
|
|
379
|
+
def resolve_github_url(url)
|
380
|
+
GithubURL.resolve(url, local_repo) if url =~ /^https?:/
|
381
|
+
end
|
382
|
+
|
266
383
|
LGHCONF = "http://help.github.com/set-your-user-name-email-and-github-token/"
|
267
384
|
|
268
385
|
# Either returns the GitHub user as set by git-config(1) or aborts
|
269
386
|
# with an error message.
|
270
|
-
def github_user(fatal = true)
|
271
|
-
if
|
387
|
+
def github_user(fatal = true, host = nil)
|
388
|
+
if local = local_repo(false)
|
389
|
+
host ||= local.default_host
|
390
|
+
host = nil if host == local.main_host
|
391
|
+
end
|
392
|
+
host = %(."#{host}") if host
|
393
|
+
if user = ENV['GITHUB_USER'] || git_config("github#{host}.user")
|
272
394
|
user
|
273
395
|
elsif fatal
|
274
|
-
|
396
|
+
if host.nil?
|
397
|
+
abort("** No GitHub user set. See #{LGHCONF}")
|
398
|
+
else
|
399
|
+
abort("** No user set for github#{host}")
|
400
|
+
end
|
275
401
|
end
|
276
402
|
end
|
277
403
|
|
278
|
-
def github_token(fatal = true)
|
279
|
-
if
|
404
|
+
def github_token(fatal = true, host = nil)
|
405
|
+
if local = local_repo(false)
|
406
|
+
host ||= local.default_host
|
407
|
+
host = nil if host == local.main_host
|
408
|
+
end
|
409
|
+
host = %(."#{host}") if host
|
410
|
+
if token = ENV['GITHUB_TOKEN'] || git_config("github#{host}.token")
|
280
411
|
token
|
281
412
|
elsif fatal
|
282
|
-
|
413
|
+
if host.nil?
|
414
|
+
abort("** No GitHub token set. See #{LGHCONF}")
|
415
|
+
else
|
416
|
+
abort("** No token set for github#{host}")
|
417
|
+
end
|
283
418
|
end
|
284
419
|
end
|
285
420
|
|