rubygems-mirror 1.0.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.
- data/.autotest +7 -0
- data/.gemtest +0 -0
- data/CHANGELOG.rdoc +5 -0
- data/Manifest.txt +12 -0
- data/README.rdoc +70 -0
- data/Rakefile +37 -0
- data/lib/rubygems/mirror.rb +88 -0
- data/lib/rubygems/mirror/command.rb +71 -0
- data/lib/rubygems/mirror/fetcher.rb +56 -0
- data/lib/rubygems/mirror/pool.rb +22 -0
- data/lib/rubygems/mirror/test_setup.rb +166 -0
- data/lib/rubygems_plugin.rb +4 -0
- data/pkg/rubygems-mirror-1.0.0/CHANGELOG.rdoc +5 -0
- data/pkg/rubygems-mirror-1.0.0/README.rdoc +70 -0
- data/test/test_gem_mirror.rb +54 -0
- metadata +185 -0
data/.autotest
ADDED
data/.gemtest
ADDED
File without changes
|
data/CHANGELOG.rdoc
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
.autotest
|
2
|
+
CHANGELOG.rdoc
|
3
|
+
Manifest.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
lib/rubygems/mirror.rb
|
7
|
+
lib/rubygems/mirror/command.rb
|
8
|
+
lib/rubygems/mirror/fetcher.rb
|
9
|
+
lib/rubygems/mirror/pool.rb
|
10
|
+
lib/rubygems/mirror/test_setup.rb
|
11
|
+
lib/rubygems_plugin.rb
|
12
|
+
test/test_gem_mirror.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
= rubygems-mirror
|
2
|
+
|
3
|
+
http://github.com/rubygems/rubygems-mirror
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
This is an update to the old `gem mirror` command. It uses net/http/persistent
|
8
|
+
and threads to grab the mirror set a little faster than the original.
|
9
|
+
Eventually it will replace `gem mirror` completely. Right now the API is not
|
10
|
+
completely stable (it will change several times before release), however, I
|
11
|
+
will maintain stability in master.
|
12
|
+
|
13
|
+
== FEATURES/PROBLEMS:
|
14
|
+
|
15
|
+
* Fast mirroring
|
16
|
+
* Limited tests - just functional
|
17
|
+
|
18
|
+
== REQUIREMENTS:
|
19
|
+
|
20
|
+
* rubygems
|
21
|
+
* net/http/persistent
|
22
|
+
|
23
|
+
== USAGE
|
24
|
+
|
25
|
+
* In a file at ~/.gem/.mirrorrc add a config that looks like the following:
|
26
|
+
|
27
|
+
---
|
28
|
+
- from: http://rubygems.org
|
29
|
+
to: /data/rubygems
|
30
|
+
|
31
|
+
* Either install the gem, then run `gem mirror`, or
|
32
|
+
* Clone then run `rake mirror:update`
|
33
|
+
|
34
|
+
== INSTALL:
|
35
|
+
|
36
|
+
* gem install rubygems-mirror
|
37
|
+
|
38
|
+
== RESOURCES
|
39
|
+
|
40
|
+
* {Website}[http://rubygems.org/]
|
41
|
+
* {Documentation}[http://rubygems.rubyforge.org/rubygems-mirror/README_rdoc.html]
|
42
|
+
* {Wiki}[http://wiki.github.com/rubygems/rubygems-mirror/]
|
43
|
+
* {Source Code}[http://github.com/rubygems/rubygems-mirror/]
|
44
|
+
* {Issues}[http://github.com/rubygems/rubygems-mirror/issues]
|
45
|
+
* {Rubyforge}[http://rubyforge.org/projects/rubygems]
|
46
|
+
|
47
|
+
== LICENSE:
|
48
|
+
|
49
|
+
(The MIT License)
|
50
|
+
|
51
|
+
Copyright (c) 2010 James Tucker, The RubyGems Team
|
52
|
+
|
53
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
54
|
+
a copy of this software and associated documentation files (the
|
55
|
+
'Software'), to deal in the Software without restriction, including
|
56
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
57
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
58
|
+
permit persons to whom the Software is furnished to do so, subject to
|
59
|
+
the following conditions:
|
60
|
+
|
61
|
+
The above copyright notice and this permission notice shall be
|
62
|
+
included in all copies or substantial portions of the Software.
|
63
|
+
|
64
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
65
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
66
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
67
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
68
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
69
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
70
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
require 'hoe'
|
4
|
+
Hoe.plugin :doofus, :git, :gemcutter
|
5
|
+
|
6
|
+
Hoe.spec 'rubygems-mirror' do
|
7
|
+
developer('James Tucker', 'raggi@rubyforge.org')
|
8
|
+
|
9
|
+
extra_dev_deps << %w[hoe-doofus >=1.0.0]
|
10
|
+
extra_dev_deps << %w[hoe-git >=1.3.0]
|
11
|
+
extra_dev_deps << %w[hoe-gemcutter >=1.0.0]
|
12
|
+
extra_dev_deps << %w[builder >=2.1.2]
|
13
|
+
extra_deps << %w[net-http-persistent >=2.1]
|
14
|
+
|
15
|
+
self.extra_rdoc_files = FileList["**/*.rdoc"]
|
16
|
+
self.history_file = "CHANGELOG.rdoc"
|
17
|
+
self.readme_file = "README.rdoc"
|
18
|
+
self.rubyforge_name = 'rubygems'
|
19
|
+
self.testlib = :minitest
|
20
|
+
end
|
21
|
+
|
22
|
+
namespace :mirror do
|
23
|
+
desc "Run the Gem::Mirror::Command"
|
24
|
+
task :update do
|
25
|
+
$:.unshift 'lib'
|
26
|
+
require 'rubygems/mirror/command'
|
27
|
+
|
28
|
+
mirror = Gem::Commands::MirrorCommand.new
|
29
|
+
mirror.execute
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
namespace :test do
|
34
|
+
task :integration do
|
35
|
+
sh Gem.ruby, '-Ilib', '-rubygems', '-S', 'gem', 'mirror'
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Gem::Mirror
|
5
|
+
autoload :Fetcher, 'rubygems/mirror/fetcher'
|
6
|
+
autoload :Pool, 'rubygems/mirror/pool'
|
7
|
+
|
8
|
+
VERSION = '1.0.0'
|
9
|
+
|
10
|
+
SPECS_FILE = "specs.#{Gem.marshal_version}"
|
11
|
+
SPECS_FILE_Z = "specs.#{Gem.marshal_version}.gz"
|
12
|
+
|
13
|
+
DEFAULT_URI = 'http://production.cf.rubygems.org/'
|
14
|
+
DEFAULT_TO = File.join(Gem.user_home, '.gem', 'mirror')
|
15
|
+
|
16
|
+
RUBY = 'ruby'
|
17
|
+
|
18
|
+
def initialize(from = DEFAULT_URI, to = DEFAULT_TO, parallelism = 10)
|
19
|
+
@from, @to = from, to
|
20
|
+
@fetcher = Fetcher.new
|
21
|
+
@pool = Pool.new(parallelism)
|
22
|
+
end
|
23
|
+
|
24
|
+
def from(*args)
|
25
|
+
File.join(@from, *args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to(*args)
|
29
|
+
File.join(@to, *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_specs
|
33
|
+
specz = to(SPECS_FILE_Z)
|
34
|
+
@fetcher.fetch(from(SPECS_FILE_Z), specz)
|
35
|
+
open(to(SPECS_FILE), 'wb') { |f| f << Gem.gunzip(File.read(specz)) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def gems
|
39
|
+
update_specs unless File.exists?(to(SPECS_FILE))
|
40
|
+
|
41
|
+
gems = Marshal.load(File.read(to(SPECS_FILE)))
|
42
|
+
gems.map! do |name, ver, plat|
|
43
|
+
# If the platform is ruby, it is not in the gem name
|
44
|
+
"#{name}-#{ver}#{"-#{plat}" unless plat == RUBY}.gem"
|
45
|
+
end
|
46
|
+
gems
|
47
|
+
end
|
48
|
+
|
49
|
+
def existing_gems
|
50
|
+
Dir[to('gems', '*.gem')].entries.map { |f| File.basename(f) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def gems_to_fetch
|
54
|
+
gems - existing_gems
|
55
|
+
end
|
56
|
+
|
57
|
+
def gems_to_delete
|
58
|
+
existing_gems - gems
|
59
|
+
end
|
60
|
+
|
61
|
+
def update_gems
|
62
|
+
gems_to_fetch.each do |g|
|
63
|
+
@pool.job do
|
64
|
+
@fetcher.fetch(from('gems', g), to('gems', g))
|
65
|
+
yield
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
@pool.run_til_done
|
70
|
+
end
|
71
|
+
|
72
|
+
def delete_gems
|
73
|
+
gems_to_delete.each do |g|
|
74
|
+
@pool.job do
|
75
|
+
File.delete(to('gems', g))
|
76
|
+
yield
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
@pool.run_til_done
|
81
|
+
end
|
82
|
+
|
83
|
+
def update
|
84
|
+
update_specs
|
85
|
+
update_gems
|
86
|
+
cleanup_gems
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'rubygems/mirror'
|
2
|
+
require 'rubygems/command'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
class Gem::Commands::MirrorCommand < Gem::Command
|
6
|
+
SUPPORTS_INFO_SIGNAL = Signal.list['INFO']
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super 'mirror', 'Mirror a gem repository'
|
10
|
+
end
|
11
|
+
|
12
|
+
def description # :nodoc:
|
13
|
+
<<-EOF
|
14
|
+
The mirror command uses the ~/.gemmirrorrc config file to mirror remote gem
|
15
|
+
repositories to a local path. The config file is a YAML document that looks
|
16
|
+
like this:
|
17
|
+
|
18
|
+
---
|
19
|
+
- from: http://gems.example.com # source repository URI
|
20
|
+
to: /path/to/mirror # destination directory
|
21
|
+
|
22
|
+
Multiple sources and destinations may be specified.
|
23
|
+
EOF
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute
|
27
|
+
config_file = File.join Gem.user_home, '.gem', '.mirrorrc'
|
28
|
+
|
29
|
+
raise "Config file #{config_file} not found" unless File.exist? config_file
|
30
|
+
|
31
|
+
mirrors = YAML.load_file config_file
|
32
|
+
|
33
|
+
raise "Invalid config file #{config_file}" unless mirrors.respond_to? :each
|
34
|
+
|
35
|
+
mirrors.each do |mir|
|
36
|
+
raise "mirror missing 'from' field" unless mir.has_key? 'from'
|
37
|
+
raise "mirror missing 'to' field" unless mir.has_key? 'to'
|
38
|
+
|
39
|
+
get_from = mir['from']
|
40
|
+
save_to = File.expand_path mir['to']
|
41
|
+
|
42
|
+
raise "Directory not found: #{save_to}" unless File.exist? save_to
|
43
|
+
raise "Not a directory: #{save_to}" unless File.directory? save_to
|
44
|
+
|
45
|
+
mirror = Gem::Mirror.new(get_from, save_to)
|
46
|
+
|
47
|
+
say "Fetching: #{mirror.from(Gem::Mirror::SPECS_FILE_Z)}"
|
48
|
+
mirror.update_specs
|
49
|
+
|
50
|
+
say "Total gems: #{mirror.gems.size}"
|
51
|
+
|
52
|
+
num_to_fetch = mirror.gems_to_fetch.size
|
53
|
+
|
54
|
+
progress = ui.progress_reporter num_to_fetch,
|
55
|
+
"Fetching #{num_to_fetch} gems"
|
56
|
+
|
57
|
+
trap(:INFO) { puts "Fetched: #{progress.count}/#{num_to_fetch}" } if SUPPORTS_INFO_SIGNAL
|
58
|
+
|
59
|
+
mirror.update_gems { progress.updated true }
|
60
|
+
|
61
|
+
num_to_delete = mirror.gems_to_delete.size
|
62
|
+
|
63
|
+
progress = ui.progress_reporter num_to_delete,
|
64
|
+
"Deleting #{num_to_delete} gems"
|
65
|
+
|
66
|
+
trap(:INFO) { puts "Fetched: #{progress.count}/#{num_to_delete}" } if SUPPORTS_INFO_SIGNAL
|
67
|
+
|
68
|
+
mirror.delete_gems { progress.updated true }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'net/http/persistent'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
class Gem::Mirror::Fetcher
|
5
|
+
# TODO beef
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@http = Net::HTTP::Persistent.new(self.class.name, :ENV)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Fetch a source path under the base uri, and put it in the same or given
|
13
|
+
# destination path under the base path.
|
14
|
+
def fetch(uri, path)
|
15
|
+
modified_time = File.exists?(path) && File.stat(path).mtime.rfc822
|
16
|
+
|
17
|
+
req = Net::HTTP::Get.new URI.parse(uri).path
|
18
|
+
req.add_field 'If-Modified-Since', modified_time if modified_time
|
19
|
+
|
20
|
+
@http.request URI(uri), req do |resp|
|
21
|
+
return handle_response(resp, path)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Handle an http response, follow redirects, etc. returns true if a file was
|
26
|
+
# downloaded, false if a 304. Raise Error on unknown responses.
|
27
|
+
def handle_response(resp, path)
|
28
|
+
case resp.code.to_i
|
29
|
+
when 304
|
30
|
+
when 302
|
31
|
+
fetch resp['location'], path
|
32
|
+
when 200
|
33
|
+
write_file(resp, path)
|
34
|
+
when 403, 404
|
35
|
+
warn "#{resp.code} on #{File.basename(path)}"
|
36
|
+
else
|
37
|
+
raise Error, "unexpected response #{resp.inspect}"
|
38
|
+
end
|
39
|
+
# TODO rescue http errors and reraise cleanly
|
40
|
+
end
|
41
|
+
|
42
|
+
# Efficiently writes an http response object to a particular path. If there
|
43
|
+
# is an error, it will remove the target file.
|
44
|
+
def write_file(resp, path)
|
45
|
+
FileUtils.mkdir_p File.dirname(path)
|
46
|
+
File.open(path, 'wb') do |output|
|
47
|
+
resp.read_body { |chunk| output << chunk }
|
48
|
+
end
|
49
|
+
true
|
50
|
+
ensure
|
51
|
+
# cleanup incomplete files, rescue perm errors etc, they're being
|
52
|
+
# raised already.
|
53
|
+
File.delete(path) rescue nil if $!
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class Gem::Mirror::Pool
|
4
|
+
def initialize(size)
|
5
|
+
@size = size
|
6
|
+
@queue = Queue.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def job(&blk)
|
10
|
+
@queue << blk
|
11
|
+
end
|
12
|
+
|
13
|
+
def run_til_done
|
14
|
+
threads = Array.new(@size) do
|
15
|
+
Thread.new { @queue.pop.call while true }
|
16
|
+
end
|
17
|
+
until @queue.empty? && @queue.num_waiting == @size
|
18
|
+
threads.each { |t| t.join(0.1) }
|
19
|
+
end
|
20
|
+
threads.each { |t| t.kill }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'stringio'
|
4
|
+
require 'webrick'
|
5
|
+
|
6
|
+
require "rubygems/user_interaction"
|
7
|
+
require "rubygems/indexer"
|
8
|
+
|
9
|
+
# All files must be proactively loaded, otherwhise when the Gem index is
|
10
|
+
# replaced, requires will fail!
|
11
|
+
require "rubygems/mirror"
|
12
|
+
require "rubygems/mirror/fetcher"
|
13
|
+
require "rubygems/mirror/pool"
|
14
|
+
|
15
|
+
class Gem::Mirror
|
16
|
+
|
17
|
+
# Provide assistance for authors of code that utilises Gem::Mirror. The
|
18
|
+
# module defines several setup and teardown methods that can be used to
|
19
|
+
# provide a new gem source on disk.
|
20
|
+
module TestSetup
|
21
|
+
# An instance of TestSetup::UI, a mock UI for RubyGems.
|
22
|
+
attr_reader :ui
|
23
|
+
|
24
|
+
# The path in which setup_gem_source will place a RubyGems source index
|
25
|
+
attr_reader :source_path
|
26
|
+
|
27
|
+
# A list of gem names in the source_path
|
28
|
+
attr_reader :source_gems
|
29
|
+
|
30
|
+
# A temporary directory where mirrors might put their data
|
31
|
+
attr_reader :mirror_path
|
32
|
+
|
33
|
+
# An rcfile pointing at the source and mirror paths
|
34
|
+
attr_reader :mirrorrc
|
35
|
+
|
36
|
+
# Path to the mock, temporary mirrorrc
|
37
|
+
attr_reader :mirrorrc_path
|
38
|
+
|
39
|
+
class UI < Gem::StreamUI
|
40
|
+
|
41
|
+
# All input and output from the Gem UI when using TestSetup will pass
|
42
|
+
# through StringIO objects placed here. For more information, see the
|
43
|
+
# arguments to Gem::StreamUI.
|
44
|
+
attr_reader :ui_ios
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
super(*(@ui_ios = Array.new(3) { StringIO.new }))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Setup a new mock UI using TestSetup::UI
|
52
|
+
def setup_ui
|
53
|
+
@old_ui = Gem::DefaultUserInteraction.ui
|
54
|
+
@ui = Gem::DefaultUserInteraction.ui = UI.new
|
55
|
+
end
|
56
|
+
|
57
|
+
# Restore RubyGems default UI
|
58
|
+
def teardown_ui
|
59
|
+
Gem::DefaultUserInteraction.ui = @old_ui if @old_ui
|
60
|
+
end
|
61
|
+
|
62
|
+
# Setup a new gem source directory containing a few gems suitable for
|
63
|
+
# testing mirrors, and place the path to that in source_path.
|
64
|
+
def setup_gem_source
|
65
|
+
@source_path = Dir.mktmpdir("test_gem_source_path_#{$$}")
|
66
|
+
|
67
|
+
Dir.mkdir gemdir = File.join(@source_path, 'gems')
|
68
|
+
|
69
|
+
@source_working = working = Dir.mktmpdir("test_gem_source_#{$$}")
|
70
|
+
|
71
|
+
Dir.mkdir File.join(working, 'lib')
|
72
|
+
|
73
|
+
gemspecs = %w[a b c].map do |name|
|
74
|
+
FileUtils.touch File.join(working, 'lib', "#{name}.rb")
|
75
|
+
Gem::Specification.new do |s|
|
76
|
+
s.platform = Gem::Platform::RUBY
|
77
|
+
s.name = name
|
78
|
+
s.version = 1.0
|
79
|
+
s.author = 'rubygems'
|
80
|
+
s.email = 'example@example.com'
|
81
|
+
s.homepage = 'http://example.com'
|
82
|
+
s.has_rdoc = false
|
83
|
+
s.description = 'desc'
|
84
|
+
s.summary = "summ"
|
85
|
+
s.require_paths = %w[lib]
|
86
|
+
s.files = %W[lib/#{name}.rb]
|
87
|
+
s.rubyforge_project = 'rubygems'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
gemspecs.each do |spec|
|
92
|
+
path = File.join(working, "#{spec.name}.gemspec")
|
93
|
+
open(path, 'w') do |io|
|
94
|
+
io.write(spec.to_ruby)
|
95
|
+
end
|
96
|
+
Dir.chdir(working) do
|
97
|
+
gem_file = Gem::Builder.new(spec).build
|
98
|
+
FileUtils.mv(gem_file, File.join(@source_path, 'gems', gem_file))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
@source_gems = Dir[File.join(gemdir, '*.gem')].map {|p|File.basename(p)}
|
103
|
+
|
104
|
+
Gem::Indexer.new(@source_path).generate_index
|
105
|
+
end
|
106
|
+
|
107
|
+
# Cleanup temporary directories that are created by setup_gem_source.
|
108
|
+
def teardown_gem_source
|
109
|
+
[@source_path, @source_working].each do |path|
|
110
|
+
FileUtils.rm_rf path
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Setup a new mirrorrc for Gem::Mirror based on a setup from
|
115
|
+
# setup_gem_source.
|
116
|
+
def setup_mirrorrc
|
117
|
+
@mirror_path = Dir.mktmpdir("test_gem_mirror_path_#{$$}")
|
118
|
+
@mirrorrc = Tempfile.new('testgemmirrorrc')
|
119
|
+
opts = {
|
120
|
+
'mirrors' => {
|
121
|
+
'from' => "http://127.0.0.1:8808/", 'to' => @mirror_path
|
122
|
+
}
|
123
|
+
}
|
124
|
+
@mirrorrc.write YAML.dump(opts)
|
125
|
+
@mirrorrc_path = @mirrorrc.path
|
126
|
+
end
|
127
|
+
|
128
|
+
# Cleanup tempfiles created by setup_mirrorrc.
|
129
|
+
def teardown_mirrorrc
|
130
|
+
FileUtils.rm_rf @mirrorrc_path if @mirrorrc_path
|
131
|
+
FileUtils.rm_rf @mirror_path if @mirror_path
|
132
|
+
end
|
133
|
+
|
134
|
+
# Starts a server using Rack that will host the gem source
|
135
|
+
def setup_server
|
136
|
+
opts = { :Port => 8808, :DocumentRoot => @source_path }
|
137
|
+
unless $DEBUG
|
138
|
+
require 'logger'
|
139
|
+
opts[:Logger] = Logger.new('/dev/null')
|
140
|
+
opts[:AccessLog] = Logger.new('/dev/null')
|
141
|
+
end
|
142
|
+
@server = WEBrick::HTTPServer.new opts
|
143
|
+
@server_thread = Thread.new { @server.start }
|
144
|
+
@server_thread.join(0.1) # pickup early errors and give it time to start
|
145
|
+
end
|
146
|
+
|
147
|
+
# Shutdown the local server hosting the source_path
|
148
|
+
def teardown_server
|
149
|
+
@server && @server.shutdown
|
150
|
+
@server_thread && @server_thread.join
|
151
|
+
end
|
152
|
+
|
153
|
+
def with_server
|
154
|
+
setup_ui
|
155
|
+
setup_gem_source
|
156
|
+
setup_mirrorrc
|
157
|
+
setup_server
|
158
|
+
yield
|
159
|
+
ensure
|
160
|
+
teardown_gem_source
|
161
|
+
teardown_server
|
162
|
+
teardown_mirrorrc
|
163
|
+
teardown_ui
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
= rubygems-mirror
|
2
|
+
|
3
|
+
http://github.com/rubygems/rubygems-mirror
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
This is an update to the old `gem mirror` command. It uses net/http/persistent
|
8
|
+
and threads to grab the mirror set a little faster than the original.
|
9
|
+
Eventually it will replace `gem mirror` completely. Right now the API is not
|
10
|
+
completely stable (it will change several times before release), however, I
|
11
|
+
will maintain stability in master.
|
12
|
+
|
13
|
+
== FEATURES/PROBLEMS:
|
14
|
+
|
15
|
+
* Fast mirroring
|
16
|
+
* Limited tests - just functional
|
17
|
+
|
18
|
+
== REQUIREMENTS:
|
19
|
+
|
20
|
+
* rubygems
|
21
|
+
* net/http/persistent
|
22
|
+
|
23
|
+
== USAGE
|
24
|
+
|
25
|
+
* In a file at ~/.gem/.mirrorrc add a config that looks like the following:
|
26
|
+
|
27
|
+
---
|
28
|
+
- from: http://rubygems.org
|
29
|
+
to: /data/rubygems
|
30
|
+
|
31
|
+
* Either install the gem, then run `gem mirror`, or
|
32
|
+
* Clone then run `rake mirror:update`
|
33
|
+
|
34
|
+
== INSTALL:
|
35
|
+
|
36
|
+
* gem install rubygems-mirror
|
37
|
+
|
38
|
+
== RESOURCES
|
39
|
+
|
40
|
+
* {Website}[http://rubygems.org/]
|
41
|
+
* {Documentation}[http://rubygems.rubyforge.org/rubygems-mirror/README_rdoc.html]
|
42
|
+
* {Wiki}[http://wiki.github.com/rubygems/rubygems-mirror/]
|
43
|
+
* {Source Code}[http://github.com/rubygems/rubygems-mirror/]
|
44
|
+
* {Issues}[http://github.com/rubygems/rubygems-mirror/issues]
|
45
|
+
* {Rubyforge}[http://rubyforge.org/projects/rubygems]
|
46
|
+
|
47
|
+
== LICENSE:
|
48
|
+
|
49
|
+
(The MIT License)
|
50
|
+
|
51
|
+
Copyright (c) 2010 James Tucker, The RubyGems Team
|
52
|
+
|
53
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
54
|
+
a copy of this software and associated documentation files (the
|
55
|
+
'Software'), to deal in the Software without restriction, including
|
56
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
57
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
58
|
+
permit persons to whom the Software is furnished to do so, subject to
|
59
|
+
the following conditions:
|
60
|
+
|
61
|
+
The above copyright notice and this permission notice shall be
|
62
|
+
included in all copies or substantial portions of the Software.
|
63
|
+
|
64
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
65
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
66
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
67
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
68
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
69
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
70
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "rubygems_plugin"
|
2
|
+
require "rubygems/mirror"
|
3
|
+
require "rubygems/mirror/test_setup"
|
4
|
+
|
5
|
+
require 'minitest/autorun' # damn you autotest.
|
6
|
+
|
7
|
+
class TestGemMirror < MiniTest::Unit::TestCase
|
8
|
+
include Gem::Mirror::TestSetup
|
9
|
+
|
10
|
+
# Used to make sure we don't raise on construction, works against defaults
|
11
|
+
def opts
|
12
|
+
['http://localhost:8808/', mirror_path, 1]
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_update_specs
|
16
|
+
with_server do
|
17
|
+
mirror = Gem::Mirror.new(*opts)
|
18
|
+
mirror.update_specs
|
19
|
+
assert File.exists?(mirror_path + "/#{Gem::Mirror::SPECS_FILE_Z}")
|
20
|
+
assert File.exists?(mirror_path + "/#{Gem::Mirror::SPECS_FILE_Z}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_update_gems
|
25
|
+
with_server do
|
26
|
+
mirror = Gem::Mirror.new(*opts)
|
27
|
+
|
28
|
+
updates = 0
|
29
|
+
mirror.update_gems { updates += 1 }
|
30
|
+
|
31
|
+
source_gems, mirror_gems = [source_path, mirror_path].map do |path|
|
32
|
+
Dir[path + '/gems/*'].map { |f| File.basename(f) }
|
33
|
+
end
|
34
|
+
|
35
|
+
assert_equal source_gems, mirror_gems
|
36
|
+
assert_equal 3, updates
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_delete_gems
|
41
|
+
with_server do
|
42
|
+
mirror = Gem::Mirror.new(*opts)
|
43
|
+
FileUtils.mkdir_p mirror.to('gems')
|
44
|
+
FileUtils.touch mirror.to('gems', 'd-1.0.gem')
|
45
|
+
|
46
|
+
updates = 0
|
47
|
+
mirror.delete_gems { updates += 1 }
|
48
|
+
|
49
|
+
gems = Dir[mirror_path + '/gems/*.gem'].map { |f| File.basename f }
|
50
|
+
assert_equal [], gems
|
51
|
+
assert_equal 1, updates
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubygems-mirror
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- James Tucker
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-10-02 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: net-http-persistent
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 1
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 1
|
32
|
+
version: "2.1"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: hoe-doofus
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 23
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 0
|
47
|
+
- 0
|
48
|
+
version: 1.0.0
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: hoe-git
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 27
|
60
|
+
segments:
|
61
|
+
- 1
|
62
|
+
- 3
|
63
|
+
- 0
|
64
|
+
version: 1.3.0
|
65
|
+
type: :development
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: hoe-gemcutter
|
69
|
+
prerelease: false
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 23
|
76
|
+
segments:
|
77
|
+
- 1
|
78
|
+
- 0
|
79
|
+
- 0
|
80
|
+
version: 1.0.0
|
81
|
+
type: :development
|
82
|
+
version_requirements: *id004
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: builder
|
85
|
+
prerelease: false
|
86
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 15
|
92
|
+
segments:
|
93
|
+
- 2
|
94
|
+
- 1
|
95
|
+
- 2
|
96
|
+
version: 2.1.2
|
97
|
+
type: :development
|
98
|
+
version_requirements: *id005
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: hoe
|
101
|
+
prerelease: false
|
102
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ~>
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 27
|
108
|
+
segments:
|
109
|
+
- 2
|
110
|
+
- 12
|
111
|
+
version: "2.12"
|
112
|
+
type: :development
|
113
|
+
version_requirements: *id006
|
114
|
+
description: |-
|
115
|
+
This is an update to the old `gem mirror` command. It uses net/http/persistent
|
116
|
+
and threads to grab the mirror set a little faster than the original.
|
117
|
+
Eventually it will replace `gem mirror` completely. Right now the API is not
|
118
|
+
completely stable (it will change several times before release), however, I
|
119
|
+
will maintain stability in master.
|
120
|
+
email:
|
121
|
+
- raggi@rubyforge.org
|
122
|
+
executables: []
|
123
|
+
|
124
|
+
extensions: []
|
125
|
+
|
126
|
+
extra_rdoc_files:
|
127
|
+
- Manifest.txt
|
128
|
+
- CHANGELOG.rdoc
|
129
|
+
- pkg/rubygems-mirror-1.0.0/CHANGELOG.rdoc
|
130
|
+
- pkg/rubygems-mirror-1.0.0/README.rdoc
|
131
|
+
- README.rdoc
|
132
|
+
files:
|
133
|
+
- .autotest
|
134
|
+
- CHANGELOG.rdoc
|
135
|
+
- Manifest.txt
|
136
|
+
- README.rdoc
|
137
|
+
- Rakefile
|
138
|
+
- lib/rubygems/mirror.rb
|
139
|
+
- lib/rubygems/mirror/command.rb
|
140
|
+
- lib/rubygems/mirror/fetcher.rb
|
141
|
+
- lib/rubygems/mirror/pool.rb
|
142
|
+
- lib/rubygems/mirror/test_setup.rb
|
143
|
+
- lib/rubygems_plugin.rb
|
144
|
+
- test/test_gem_mirror.rb
|
145
|
+
- pkg/rubygems-mirror-1.0.0/CHANGELOG.rdoc
|
146
|
+
- pkg/rubygems-mirror-1.0.0/README.rdoc
|
147
|
+
- .gemtest
|
148
|
+
homepage: http://github.com/rubygems/rubygems-mirror
|
149
|
+
licenses: []
|
150
|
+
|
151
|
+
metadata: {}
|
152
|
+
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options:
|
155
|
+
- --main
|
156
|
+
- README.rdoc
|
157
|
+
require_paths:
|
158
|
+
- lib
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
none: false
|
161
|
+
requirements:
|
162
|
+
- - ">="
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
hash: 3
|
165
|
+
segments:
|
166
|
+
- 0
|
167
|
+
version: "0"
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
hash: 3
|
174
|
+
segments:
|
175
|
+
- 0
|
176
|
+
version: "0"
|
177
|
+
requirements: []
|
178
|
+
|
179
|
+
rubyforge_project: rubygems
|
180
|
+
rubygems_version: 1.8.10
|
181
|
+
signing_key:
|
182
|
+
specification_version: 4
|
183
|
+
summary: This is an update to the old `gem mirror` command
|
184
|
+
test_files:
|
185
|
+
- test/test_gem_mirror.rb
|