hub 1.6.1 → 1.7.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.
Potentially problematic release.
This version of hub might be problematic. Click here for more details.
- data/README.md +94 -60
- data/Rakefile +74 -38
- data/lib/hub/args.rb +16 -3
- data/lib/hub/commands.rb +340 -101
- data/lib/hub/context.rb +270 -112
- data/lib/hub/runner.rb +4 -2
- data/lib/hub/standalone.rb +13 -7
- data/lib/hub/version.rb +1 -1
- data/man/hub.1 +162 -80
- data/man/hub.1.html +163 -96
- data/man/hub.1.ronn +84 -167
- data/test/alias_test.rb +0 -1
- data/test/helper.rb +8 -8
- data/test/hub_test.rb +395 -71
- data/test/standalone_test.rb +0 -1
- metadata +8 -6
data/lib/hub/commands.rb
CHANGED
@@ -1,7 +1,4 @@
|
|
1
1
|
module Hub
|
2
|
-
# See context.rb
|
3
|
-
module Context; end
|
4
|
-
|
5
2
|
# The Commands module houses the git commands that hub
|
6
3
|
# lovingly wraps. If a method exists here, it is expected to have a
|
7
4
|
# corresponding git command which either gets run before or after
|
@@ -34,12 +31,16 @@ module Hub
|
|
34
31
|
instance_methods.each { |m| undef_method(m) unless m =~ /(^__|send|to\?$)/ }
|
35
32
|
extend self
|
36
33
|
|
37
|
-
#
|
34
|
+
# provides git interrogation methods
|
38
35
|
extend Context
|
39
36
|
|
40
|
-
API_REPO
|
41
|
-
API_FORK
|
42
|
-
API_CREATE
|
37
|
+
API_REPO = 'http://github.com/api/v2/yaml/repos/show/%s/%s'
|
38
|
+
API_FORK = 'https://github.com/api/v2/yaml/repos/fork/%s/%s'
|
39
|
+
API_CREATE = 'https://github.com/api/v2/yaml/repos/create'
|
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-]+))?$/
|
43
44
|
|
44
45
|
def run(args)
|
45
46
|
slurp_global_flags(args)
|
@@ -57,6 +58,106 @@ module Hub
|
|
57
58
|
args[0, 1] = expanded_args if expanded_args
|
58
59
|
send(cmd, args)
|
59
60
|
end
|
61
|
+
rescue Errno::ENOENT
|
62
|
+
if $!.message.include? "No such file or directory - git"
|
63
|
+
abort "Error: `git` command not found"
|
64
|
+
else
|
65
|
+
raise
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# $ hub pull-request
|
70
|
+
# $ hub pull-request "My humble contribution"
|
71
|
+
# $ hub pull-request -i 92
|
72
|
+
# $ hub pull-request https://github.com/rtomayko/tilt/issues/92
|
73
|
+
def pull_request(args)
|
74
|
+
args.shift
|
75
|
+
options = { }
|
76
|
+
force = explicit_owner = false
|
77
|
+
base_project = local_repo.main_project
|
78
|
+
head_project = local_repo.current_project
|
79
|
+
|
80
|
+
from_github_ref = lambda do |ref, context_project|
|
81
|
+
if ref.index(':')
|
82
|
+
owner, ref = ref.split(':', 2)
|
83
|
+
project = github_project(context_project.name, owner)
|
84
|
+
end
|
85
|
+
[project || context_project, ref]
|
86
|
+
end
|
87
|
+
|
88
|
+
while arg = args.shift
|
89
|
+
case arg
|
90
|
+
when '-f'
|
91
|
+
force = true
|
92
|
+
when '-b'
|
93
|
+
base_project, options[:base] = from_github_ref.call(args.shift, base_project)
|
94
|
+
when '-h'
|
95
|
+
head = args.shift
|
96
|
+
explicit_owner = !!head.index(':')
|
97
|
+
head_project, options[:head] = from_github_ref.call(head, head_project)
|
98
|
+
when '-i'
|
99
|
+
options[:issue] = args.shift
|
100
|
+
when %r{^https?://github.com/([^/]+/[^/]+)/issues/(\d+)}
|
101
|
+
options[:issue] = $2
|
102
|
+
base_project = github_project($1)
|
103
|
+
else
|
104
|
+
if !options[:title] then options[:title] = arg
|
105
|
+
else
|
106
|
+
abort "invalid argument: #{arg}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
options[:project] = base_project
|
112
|
+
options[:base] ||= master_branch.short_name
|
113
|
+
|
114
|
+
if tracked_branch = options[:head].nil? && current_branch.upstream
|
115
|
+
if base_project == head_project and tracked_branch.short_name == options[:base]
|
116
|
+
$stderr.puts "Aborted: head branch is the same as base (#{options[:base].inspect})"
|
117
|
+
warn "(use `-h <branch>` to specify an explicit pull request head)"
|
118
|
+
abort
|
119
|
+
end
|
120
|
+
end
|
121
|
+
options[:head] ||= (tracked_branch || current_branch).short_name
|
122
|
+
|
123
|
+
if head_project.owner != github_user and !tracked_branch and !explicit_owner
|
124
|
+
head_project = github_project(head_project.name, github_user)
|
125
|
+
end
|
126
|
+
|
127
|
+
remote_branch = "#{head_project.remote}/#{options[:head]}"
|
128
|
+
options[:head] = "#{head_project.owner}:#{options[:head]}"
|
129
|
+
|
130
|
+
if !force and tracked_branch and local_commits = git_command("rev-list --cherry #{remote_branch}...")
|
131
|
+
$stderr.puts "Aborted: #{local_commits.split("\n").size} commits are not yet pushed to #{remote_branch}"
|
132
|
+
warn "(use `-f` to force submit a pull request anyway)"
|
133
|
+
abort
|
134
|
+
end
|
135
|
+
|
136
|
+
if args.noop?
|
137
|
+
puts "Would reqest a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}"
|
138
|
+
exit
|
139
|
+
end
|
140
|
+
|
141
|
+
unless options[:title] or options[:issue]
|
142
|
+
base_branch = "#{base_project.remote}/#{options[:base]}"
|
143
|
+
changes = git_command "log --no-color --pretty=medium --cherry %s...%s" %
|
144
|
+
[base_branch, remote_branch]
|
145
|
+
|
146
|
+
options[:title], options[:body] = pullrequest_editmsg(changes) { |msg|
|
147
|
+
msg.puts "# Requesting a pull to #{base_project.owner}:#{options[:base]} from #{options[:head]}"
|
148
|
+
msg.puts "#"
|
149
|
+
msg.puts "# Write a message for this pull request. The first block"
|
150
|
+
msg.puts "# of text is the title and the rest is description."
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
pull = create_pullrequest(options)
|
155
|
+
|
156
|
+
args.executable = 'echo'
|
157
|
+
args.replace [pull['html_url']]
|
158
|
+
rescue HTTPExceptions
|
159
|
+
display_http_exception("creating pull request", $!.response)
|
160
|
+
exit 1
|
60
161
|
end
|
61
162
|
|
62
163
|
# $ hub clone rtomayko/tilt
|
@@ -79,13 +180,14 @@ module Hub
|
|
79
180
|
arg = args[idx]
|
80
181
|
if arg.index('-') == 0
|
81
182
|
idx += 1 if arg =~ has_values
|
82
|
-
|
83
|
-
# Bail out early for URLs and local paths.
|
84
|
-
break
|
85
|
-
elsif arg.scan('/').size <= 1 && !arg.include?(':')
|
183
|
+
else
|
86
184
|
# $ hub clone rtomayko/tilt
|
87
185
|
# $ hub clone tilt
|
88
|
-
|
186
|
+
if arg =~ NAME_WITH_OWNER_RE
|
187
|
+
project = github_project(arg)
|
188
|
+
ssh ||= args[0] != 'submodule' && project.owner == github_user(false)
|
189
|
+
args[idx] = project.git_url(:private => ssh, :https => https_protocol?)
|
190
|
+
end
|
89
191
|
break
|
90
192
|
end
|
91
193
|
idx += 1
|
@@ -127,17 +229,17 @@ module Hub
|
|
127
229
|
# $ hub remote add origin
|
128
230
|
# > git remote add origin git://github.com/YOUR_LOGIN/THIS_REPO.git
|
129
231
|
def remote(args)
|
130
|
-
|
232
|
+
if %w[add set-url].include?(args[1]) && args.last =~ NAME_WITH_OWNER_RE
|
233
|
+
user, repo = $1, $2 || repo_name
|
234
|
+
else
|
235
|
+
return # do not touch arguments
|
236
|
+
end
|
131
237
|
|
132
238
|
ssh = args.delete('-p')
|
133
239
|
|
134
|
-
# user/repo
|
135
|
-
args.last =~ /\b(.+?)(?:\/(.+))?$/
|
136
|
-
user, repo = $1, $2
|
137
|
-
|
138
240
|
if args.words[2] == 'origin' && args.words[3].nil?
|
139
241
|
# Origin special case triggers default user/repo
|
140
|
-
user
|
242
|
+
user, repo = github_user, repo_name
|
141
243
|
elsif args.words[-2] == args.words[1]
|
142
244
|
# rtomayko/tilt => rtomayko
|
143
245
|
# Make sure you dance around flags.
|
@@ -147,10 +249,10 @@ module Hub
|
|
147
249
|
# They're specifying the remote name manually (e.g.
|
148
250
|
# git remote add blah rtomayko/tilt), so just drop the last
|
149
251
|
# argument.
|
150
|
-
args.
|
252
|
+
args.pop
|
151
253
|
end
|
152
254
|
|
153
|
-
args <<
|
255
|
+
args << git_url(user, repo, :private => ssh)
|
154
256
|
end
|
155
257
|
|
156
258
|
# $ hub fetch mislav
|
@@ -188,8 +290,31 @@ module Hub
|
|
188
290
|
|
189
291
|
if names.any?
|
190
292
|
names.each do |name|
|
191
|
-
args.before ['remote', 'add', name,
|
293
|
+
args.before ['remote', 'add', name, git_url(name)]
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# $ git checkout https://github.com/defunkt/hub/pull/73
|
299
|
+
# > git remote add -f -t feature git://github:com/mislav/hub.git
|
300
|
+
# > git checkout -b mislav-feature mislav/feature
|
301
|
+
def checkout(args)
|
302
|
+
if (2..3) === args.length and args[1] =~ %r{https?://github.com/(.+?)/(.+?)/pull/(\d+)}
|
303
|
+
owner, repo, pull_id = $1, $2, $3
|
304
|
+
|
305
|
+
load_net_http
|
306
|
+
pull_body = Net::HTTP.get URI(API_PULL % File.join(owner, repo, pull_id))
|
307
|
+
|
308
|
+
user, branch = pull_body.match(/"label":\s*"(.+?)"/)[1].split(':', 2)
|
309
|
+
new_branch_name = args[2] || "#{user}-#{branch}"
|
310
|
+
|
311
|
+
if remotes.include? user
|
312
|
+
args.before ['remote', 'set-branches', '--add', user, branch]
|
313
|
+
args.before ['fetch', user, "+refs/heads/#{branch}:refs/remotes/#{user}/#{branch}"]
|
314
|
+
else
|
315
|
+
args.before ['remote', 'add', '-f', '-t', branch, user, github_project(repo, user).git_url]
|
192
316
|
end
|
317
|
+
args[1..-1] = ['-b', new_branch_name, "#{user}/#{branch}"]
|
193
318
|
end
|
194
319
|
end
|
195
320
|
|
@@ -210,7 +335,7 @@ module Hub
|
|
210
335
|
when %r{^(?:https?:)//github.com/(.+?)/(.+?)/commit/([a-f0-9]{7,40})}
|
211
336
|
user, repo, sha = $1, $2, $3
|
212
337
|
args[args.index(ref)] = sha
|
213
|
-
when /^(\w+)@([a-
|
338
|
+
when /^(\w+)@([a-f0-9]{7,40})$/
|
214
339
|
user, repo, sha = $1, nil, $2
|
215
340
|
args[args.index(ref)] = sha
|
216
341
|
else
|
@@ -220,12 +345,11 @@ module Hub
|
|
220
345
|
if user
|
221
346
|
if user == repo_owner
|
222
347
|
# fetch from origin if the repo belongs to the user
|
223
|
-
args.before ['fetch',
|
348
|
+
args.before ['fetch', origin_remote]
|
224
349
|
elsif remotes.include?(user)
|
225
350
|
args.before ['fetch', user]
|
226
351
|
else
|
227
|
-
|
228
|
-
args.before ['remote', 'add', '-f', user, remote_url]
|
352
|
+
args.before ['remote', 'add', '-f', user, git_url(user, repo)]
|
229
353
|
end
|
230
354
|
end
|
231
355
|
end
|
@@ -248,13 +372,18 @@ module Hub
|
|
248
372
|
end
|
249
373
|
end
|
250
374
|
|
375
|
+
# $ hub apply https://github.com/defunkt/hub/pull/55
|
376
|
+
# > curl https://github.com/defunkt/hub/pull/55.patch -o /tmp/55.patch
|
377
|
+
# > git apply /tmp/55.patch
|
378
|
+
alias_method :apply, :am
|
379
|
+
|
251
380
|
# $ hub init -g
|
252
381
|
# > git init
|
253
382
|
# > git remote add origin git@github.com:USER/REPO.git
|
254
383
|
def init(args)
|
255
384
|
if args.delete('-g')
|
256
|
-
url =
|
257
|
-
args.after
|
385
|
+
url = git_url(github_user, repo_name, :private => true)
|
386
|
+
args.after ['remote', 'add', 'origin', url]
|
258
387
|
end
|
259
388
|
end
|
260
389
|
|
@@ -265,22 +394,21 @@ module Hub
|
|
265
394
|
# can't do anything without token and original owner name
|
266
395
|
if github_user && github_token && repo_owner
|
267
396
|
if repo_exists?(github_user)
|
268
|
-
|
397
|
+
warn "#{github_user}/#{repo_name} already exists on GitHub"
|
269
398
|
else
|
270
|
-
fork_repo
|
399
|
+
fork_repo unless args.noop?
|
271
400
|
end
|
272
401
|
|
273
402
|
if args.include?('--no-remote')
|
274
403
|
exit
|
275
404
|
else
|
276
|
-
url =
|
405
|
+
url = git_url(github_user, repo_name, :private => true)
|
277
406
|
args.replace %W"remote add -f #{github_user} #{url}"
|
278
|
-
args.after
|
407
|
+
args.after 'echo', ['new remote:', github_user]
|
279
408
|
end
|
280
409
|
end
|
281
|
-
rescue
|
282
|
-
|
283
|
-
warn "error creating fork: #{response.message} (HTTP #{response.code})"
|
410
|
+
rescue HTTPExceptions
|
411
|
+
display_http_exception("creating fork", $!.response)
|
284
412
|
exit 1
|
285
413
|
end
|
286
414
|
|
@@ -289,12 +417,12 @@ module Hub
|
|
289
417
|
# > git remote add -f origin git@github.com:YOUR_USER/CURRENT_REPO.git
|
290
418
|
def create(args)
|
291
419
|
if !is_repo?
|
292
|
-
|
293
|
-
|
294
|
-
elsif github_user && github_token
|
420
|
+
abort "'create' must be run from inside a git repository"
|
421
|
+
elsif owner = github_user and github_token
|
295
422
|
args.shift
|
296
423
|
options = {}
|
297
424
|
options[:private] = true if args.delete('-p')
|
425
|
+
new_repo_name = nil
|
298
426
|
|
299
427
|
until args.empty?
|
300
428
|
case arg = args.shift
|
@@ -303,20 +431,26 @@ module Hub
|
|
303
431
|
when '-h'
|
304
432
|
options[:homepage] = args.shift
|
305
433
|
else
|
306
|
-
|
307
|
-
|
434
|
+
if arg =~ /^[^-]/ and new_repo_name.nil?
|
435
|
+
new_repo_name = arg
|
436
|
+
owner, new_repo_name = new_repo_name.split('/', 2) if new_repo_name.index('/')
|
437
|
+
else
|
438
|
+
abort "invalid argument: #{arg}"
|
439
|
+
end
|
308
440
|
end
|
309
441
|
end
|
442
|
+
new_repo_name ||= repo_name
|
443
|
+
repo_with_owner = "#{owner}/#{new_repo_name}"
|
310
444
|
|
311
|
-
if repo_exists?(
|
312
|
-
|
445
|
+
if repo_exists?(owner, new_repo_name)
|
446
|
+
warn "#{repo_with_owner} already exists on GitHub"
|
313
447
|
action = "set remote origin"
|
314
448
|
else
|
315
449
|
action = "created repository"
|
316
|
-
create_repo(options)
|
450
|
+
create_repo(repo_with_owner, options) unless args.noop?
|
317
451
|
end
|
318
452
|
|
319
|
-
url =
|
453
|
+
url = git_url(owner, new_repo_name, :private => true)
|
320
454
|
|
321
455
|
if remotes.first != 'origin'
|
322
456
|
args.replace %W"remote add -f origin #{url}"
|
@@ -324,11 +458,10 @@ module Hub
|
|
324
458
|
args.replace %W"remote -v"
|
325
459
|
end
|
326
460
|
|
327
|
-
args.after
|
461
|
+
args.after 'echo', ["#{action}:", repo_with_owner]
|
328
462
|
end
|
329
|
-
rescue
|
330
|
-
|
331
|
-
warn "error creating repository: #{response.message} (HTTP #{response.code})"
|
463
|
+
rescue HTTPExceptions
|
464
|
+
display_http_exception("creating repository", $!.response)
|
332
465
|
exit 1
|
333
466
|
end
|
334
467
|
|
@@ -338,7 +471,7 @@ module Hub
|
|
338
471
|
def push(args)
|
339
472
|
return if args[1].nil? || !args[1].index(',')
|
340
473
|
|
341
|
-
branch = (args[2] ||=
|
474
|
+
branch = (args[2] ||= current_branch.short_name)
|
342
475
|
remotes = args[1].split(',')
|
343
476
|
args[1] = remotes.shift
|
344
477
|
|
@@ -364,40 +497,37 @@ module Hub
|
|
364
497
|
def browse(args)
|
365
498
|
args.shift
|
366
499
|
browse_command(args) do
|
367
|
-
user = repo = nil
|
368
500
|
dest = args.shift
|
369
501
|
dest = nil if dest == '--'
|
370
502
|
|
371
503
|
if dest
|
372
504
|
# $ hub browse pjhyett/github-services
|
373
505
|
# $ hub browse github-services
|
374
|
-
|
375
|
-
elsif repo_user
|
376
|
-
# $ hub browse
|
377
|
-
user = repo_user
|
506
|
+
project = github_project dest
|
378
507
|
else
|
379
|
-
|
508
|
+
# $ hub browse
|
509
|
+
project = current_project
|
380
510
|
end
|
381
511
|
|
382
|
-
|
512
|
+
abort "Usage: hub browse [<USER>/]<REPOSITORY>" unless project
|
383
513
|
|
384
514
|
# $ hub browse -- wiki
|
385
|
-
case subpage = args.shift
|
515
|
+
path = case subpage = args.shift
|
386
516
|
when 'commits'
|
387
|
-
branch = (!dest &&
|
388
|
-
|
517
|
+
branch = (!dest && current_branch.upstream) || master_branch
|
518
|
+
"/commits/#{branch.short_name}"
|
389
519
|
when 'tree', NilClass
|
390
|
-
branch = !dest &&
|
391
|
-
|
520
|
+
branch = !dest && current_branch.upstream
|
521
|
+
"/tree/#{branch.short_name}" if branch and !branch.master?
|
392
522
|
else
|
393
|
-
|
523
|
+
"/#{subpage}"
|
394
524
|
end
|
395
525
|
|
396
|
-
|
526
|
+
project.web_url(path)
|
397
527
|
end
|
398
528
|
end
|
399
529
|
|
400
|
-
# $ hub compare 1.0
|
530
|
+
# $ hub compare 1.0..fix
|
401
531
|
# > open https://github.com/CURRENT_REPO/compare/1.0...fix
|
402
532
|
# $ hub compare refactor
|
403
533
|
# > open https://github.com/CURRENT_REPO/compare/refactor
|
@@ -409,17 +539,23 @@ module Hub
|
|
409
539
|
args.shift
|
410
540
|
browse_command(args) do
|
411
541
|
if args.empty?
|
412
|
-
branch =
|
413
|
-
if branch
|
414
|
-
range
|
542
|
+
branch = current_branch.upstream
|
543
|
+
if branch and not branch.master?
|
544
|
+
range = branch.short_name
|
545
|
+
project = current_project
|
415
546
|
else
|
416
547
|
abort "Usage: hub compare [USER] [<START>...]<END>"
|
417
548
|
end
|
418
549
|
else
|
419
|
-
|
420
|
-
|
550
|
+
sha_or_tag = /(\w{1,2}|\w[\w.-]+\w)/
|
551
|
+
# replaces two dots with three: "sha1...sha2"
|
552
|
+
range = args.pop.sub(/^#{sha_or_tag}\.\.#{sha_or_tag}$/, '\1...\2')
|
553
|
+
project = if owner = args.pop then github_project(nil, owner)
|
554
|
+
else current_project
|
555
|
+
end
|
421
556
|
end
|
422
|
-
|
557
|
+
|
558
|
+
project.web_url "/compare/#{range}"
|
423
559
|
end
|
424
560
|
end
|
425
561
|
|
@@ -433,7 +569,7 @@ module Hub
|
|
433
569
|
def hub(args)
|
434
570
|
return help(args) unless args[1] == 'standalone'
|
435
571
|
require 'hub/standalone'
|
436
|
-
puts Hub::Standalone.build
|
572
|
+
$stdout.puts Hub::Standalone.build
|
437
573
|
exit
|
438
574
|
rescue LoadError
|
439
575
|
abort "hub is running in standalone mode."
|
@@ -486,9 +622,7 @@ module Hub
|
|
486
622
|
# > git version
|
487
623
|
# (print hub version)
|
488
624
|
def version(args)
|
489
|
-
args.after
|
490
|
-
puts "hub version %s" % Version
|
491
|
-
end
|
625
|
+
args.after 'echo', ['hub version', Version]
|
492
626
|
end
|
493
627
|
alias_method "--version", :version
|
494
628
|
|
@@ -508,13 +642,21 @@ module Hub
|
|
508
642
|
end
|
509
643
|
alias_method "--help", :help
|
510
644
|
|
645
|
+
private
|
646
|
+
#
|
647
|
+
# Helper methods are private so they cannot be invoked
|
648
|
+
# from the command line.
|
649
|
+
#
|
650
|
+
|
511
651
|
# The text print when `hub help` is run, kept in its own method
|
512
652
|
# for the convenience of the author.
|
513
653
|
def improved_help_text
|
514
654
|
<<-help
|
515
|
-
usage: git [--version] [--exec-path[
|
516
|
-
|
517
|
-
|
655
|
+
usage: git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
|
656
|
+
[-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
|
657
|
+
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
|
658
|
+
[-c name=value] [--help]
|
659
|
+
<command> [<args>]
|
518
660
|
|
519
661
|
Basic Commands:
|
520
662
|
init Create an empty git repository or reinitialize an existing one
|
@@ -548,16 +690,10 @@ Advanced commands:
|
|
548
690
|
bisect Find by binary search the change that introduced a bug
|
549
691
|
grep Print files with lines matching a pattern in your codebase
|
550
692
|
|
551
|
-
See 'git help
|
693
|
+
See 'git help <command>' for more information on a specific command.
|
552
694
|
help
|
553
695
|
end
|
554
696
|
|
555
|
-
private
|
556
|
-
#
|
557
|
-
# Helper methods are private so they cannot be invoked
|
558
|
-
# from the command line.
|
559
|
-
#
|
560
|
-
|
561
697
|
# Extract global flags from the front of the arguments list.
|
562
698
|
# Makes sure important ones are supplied for calls to subcommands.
|
563
699
|
#
|
@@ -570,7 +706,7 @@ help
|
|
570
706
|
# Special: `--version`, `--help` are replaced with "version" and "help".
|
571
707
|
# Ignored: `--exec-path`, `--html-path` are kept in args list untouched.
|
572
708
|
def slurp_global_flags(args)
|
573
|
-
flags = %w[ -c -p --paginate --no-pager --no-replace-objects --bare --version --help ]
|
709
|
+
flags = %w[ --noop -c -p --paginate --no-pager --no-replace-objects --bare --version --help ]
|
574
710
|
flags2 = %w[ --exec-path= --git-dir= --work-tree= ]
|
575
711
|
|
576
712
|
# flags that should be present in subcommands, too
|
@@ -581,6 +717,8 @@ help
|
|
581
717
|
while args[0] && (flags.include?(args[0]) || flags2.any? {|f| args[0].index(f) == 0 })
|
582
718
|
flag = args.shift
|
583
719
|
case flag
|
720
|
+
when '--noop'
|
721
|
+
args.noop!
|
584
722
|
when '--version', '--help'
|
585
723
|
args.unshift flag.sub('--', '')
|
586
724
|
when '-c'
|
@@ -588,7 +726,7 @@ help
|
|
588
726
|
config_pair = args.shift
|
589
727
|
# add configuration to our local cache
|
590
728
|
key, value = config_pair.split('=', 2)
|
591
|
-
|
729
|
+
git_reader.stub_config_value(key, value)
|
592
730
|
|
593
731
|
globals << flag << config_pair
|
594
732
|
when '-p', '--paginate', '--no-pager'
|
@@ -598,25 +736,26 @@ help
|
|
598
736
|
end
|
599
737
|
end
|
600
738
|
|
601
|
-
|
602
|
-
args.
|
739
|
+
git_reader.add_exec_flags(globals)
|
740
|
+
args.add_exec_flags(globals)
|
741
|
+
args.add_exec_flags(locals)
|
603
742
|
end
|
604
743
|
|
605
744
|
# Handles common functionality of browser commands like `browse`
|
606
745
|
# and `compare`. Yields a block that returns params for `github_url`.
|
607
746
|
def browse_command(args)
|
608
747
|
url_only = args.delete('-u')
|
609
|
-
|
610
|
-
|
748
|
+
warn "Warning: the `-p` flag has no effect anymore" if args.delete('-p')
|
749
|
+
url = yield
|
611
750
|
|
612
751
|
args.executable = url_only ? 'echo' : browser_launcher
|
613
|
-
args.push
|
752
|
+
args.push url
|
614
753
|
end
|
615
754
|
|
616
755
|
# Returns the terminal-formatted manpage, ready to be printed to
|
617
756
|
# the screen.
|
618
757
|
def hub_manpage
|
619
|
-
|
758
|
+
abort "** Can't find groff(1)" unless command?('groff')
|
620
759
|
|
621
760
|
require 'open3'
|
622
761
|
out = nil
|
@@ -659,7 +798,7 @@ help
|
|
659
798
|
|
660
799
|
# http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby
|
661
800
|
def page_stdout
|
662
|
-
return
|
801
|
+
return if not $stdout.tty? or windows?
|
663
802
|
|
664
803
|
read, write = IO.pipe
|
665
804
|
|
@@ -692,9 +831,9 @@ help
|
|
692
831
|
end
|
693
832
|
|
694
833
|
# Determines whether a user has a fork of the current repo on GitHub.
|
695
|
-
def repo_exists?(user)
|
696
|
-
|
697
|
-
url = API_REPO % [user,
|
834
|
+
def repo_exists?(user, repo = repo_name)
|
835
|
+
load_net_http
|
836
|
+
url = API_REPO % [user, repo]
|
698
837
|
Net::HTTPSuccess === Net::HTTP.get_response(URI(url))
|
699
838
|
end
|
700
839
|
|
@@ -702,23 +841,79 @@ help
|
|
702
841
|
#
|
703
842
|
# Returns nothing.
|
704
843
|
def fork_repo
|
705
|
-
|
706
|
-
response =
|
844
|
+
load_net_http
|
845
|
+
response = http_post API_FORK % [repo_owner, repo_name]
|
707
846
|
response.error! unless Net::HTTPSuccess === response
|
708
847
|
end
|
709
848
|
|
710
849
|
# Creates a new repo using the GitHub API.
|
711
850
|
#
|
712
851
|
# Returns nothing.
|
713
|
-
def create_repo(options = {})
|
714
|
-
|
715
|
-
params = {'login' => github_user, 'token' => github_token, 'name' => repo_name}
|
852
|
+
def create_repo(name, options = {})
|
853
|
+
params = {'name' => name.sub(/^#{github_user}\//, '')}
|
716
854
|
params['public'] = '0' if options[:private]
|
717
855
|
params['description'] = options[:description] if options[:description]
|
718
856
|
params['homepage'] = options[:homepage] if options[:homepage]
|
719
857
|
|
720
|
-
|
858
|
+
load_net_http
|
859
|
+
response = http_post(API_CREATE, params)
|
860
|
+
response.error! unless Net::HTTPSuccess === response
|
861
|
+
end
|
862
|
+
|
863
|
+
# Returns parsed data from the new pull request.
|
864
|
+
def create_pullrequest(options)
|
865
|
+
project = options.fetch(:project)
|
866
|
+
params = {
|
867
|
+
'pull[base]' => options.fetch(:base),
|
868
|
+
'pull[head]' => options.fetch(:head)
|
869
|
+
}
|
870
|
+
params['pull[issue]'] = options[:issue] if options[:issue]
|
871
|
+
params['pull[title]'] = options[:title] if options[:title]
|
872
|
+
params['pull[body]'] = options[:body] if options[:body]
|
873
|
+
|
874
|
+
load_net_http
|
875
|
+
response = http_post(API_PULLREQUEST % [project.owner, project.name], params)
|
721
876
|
response.error! unless Net::HTTPSuccess === response
|
877
|
+
# GitHub bug: although we request YAML, it returns JSON
|
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
|
884
|
+
end
|
885
|
+
|
886
|
+
def pullrequest_editmsg(changes)
|
887
|
+
message_file = File.join(git_dir, 'PULLREQ_EDITMSG')
|
888
|
+
File.open(message_file, 'w') { |msg|
|
889
|
+
msg.puts
|
890
|
+
yield msg
|
891
|
+
if changes
|
892
|
+
msg.puts "#\n# Changes:\n#"
|
893
|
+
msg.puts changes.gsub(/^/, '# ').gsub(/ +$/, '')
|
894
|
+
end
|
895
|
+
}
|
896
|
+
edit_cmd = Array(git_editor).dup << message_file
|
897
|
+
system(*edit_cmd)
|
898
|
+
abort "can't open text editor for pull request message" unless $?.success?
|
899
|
+
title, body = read_editmsg(message_file)
|
900
|
+
abort "Aborting due to empty pull request title" unless title
|
901
|
+
[title, body]
|
902
|
+
end
|
903
|
+
|
904
|
+
def read_editmsg(file)
|
905
|
+
title, body = '', ''
|
906
|
+
File.open(file, 'r') { |msg|
|
907
|
+
msg.each_line do |line|
|
908
|
+
next if line.index('#') == 0
|
909
|
+
((body.empty? and line =~ /\S/) ? title : body) << line
|
910
|
+
end
|
911
|
+
}
|
912
|
+
title.tr!("\n", ' ')
|
913
|
+
title.strip!
|
914
|
+
body.strip!
|
915
|
+
|
916
|
+
[title =~ /\S/ ? title : nil, body =~ /\S/ ? body : nil]
|
722
917
|
end
|
723
918
|
|
724
919
|
def expand_alias(cmd)
|
@@ -730,5 +925,49 @@ help
|
|
730
925
|
end
|
731
926
|
end
|
732
927
|
|
928
|
+
def http_post(url, params = nil)
|
929
|
+
url = URI(url)
|
930
|
+
post = Net::HTTP::Post.new(url.request_uri)
|
931
|
+
post.basic_auth "#{github_user}/token", github_token
|
932
|
+
post.set_form_data params if params
|
933
|
+
|
934
|
+
port = url.port
|
935
|
+
if use_ssl = 'https' == url.scheme and not use_ssl?
|
936
|
+
# ruby compiled without openssl
|
937
|
+
use_ssl = false
|
938
|
+
port = 80
|
939
|
+
end
|
940
|
+
|
941
|
+
http = Net::HTTP.new(url.host, port)
|
942
|
+
if http.use_ssl = use_ssl
|
943
|
+
# TODO: SSL peer verification
|
944
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
945
|
+
end
|
946
|
+
http.start { http.request(post) }
|
947
|
+
end
|
948
|
+
|
949
|
+
def load_net_http
|
950
|
+
require 'net/https'
|
951
|
+
rescue LoadError
|
952
|
+
require 'net/http'
|
953
|
+
end
|
954
|
+
|
955
|
+
def use_ssl?
|
956
|
+
defined? ::OpenSSL
|
957
|
+
end
|
958
|
+
|
959
|
+
# Fake exception type for net/http exception handling.
|
960
|
+
# Necessary because net/http may or may not be loaded at the time.
|
961
|
+
module HTTPExceptions
|
962
|
+
def self.===(exception)
|
963
|
+
exception.class.ancestors.map {|a| a.to_s }.include? 'Net::HTTPExceptions'
|
964
|
+
end
|
965
|
+
end
|
966
|
+
|
967
|
+
def display_http_exception(action, response)
|
968
|
+
$stderr.puts "Error #{action}: #{response.message} (HTTP #{response.code})"
|
969
|
+
warn "Check your token configuration (`git config github.token`)" if response.code.to_i == 401
|
970
|
+
end
|
971
|
+
|
733
972
|
end
|
734
973
|
end
|