redis-copy 0.0.5 → 0.0.6

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/.gitignore CHANGED
@@ -14,4 +14,5 @@ rdoc
14
14
  spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
+ spec/db
17
18
  tmp
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ script: "bundle exec rake run"
3
+
4
+ rvm:
5
+ - 1.9.3
6
+ gemfile:
7
+ - .travis/Gemfile.redis-gem-3.0
8
+ - .travis/Gemfile.redis-gem-master
9
+ env:
10
+ global:
11
+ - TIMEOUT=1
12
+ matrix:
13
+ - REDIS_BRANCH=2.4
14
+ - REDIS_BRANCH=2.6
15
+ - REDIS_BRANCH=2.8
16
+ - REDIS_BRANCH=unstable
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+ source 'https://rubygems.org'
3
+
4
+ gem 'redis', '~> 3.0'
5
+
6
+ gemspec :path => '../'
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ redis-copy (0.0.5)
5
+ activesupport
6
+ redis
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (4.0.1)
12
+ i18n (~> 0.6, >= 0.6.4)
13
+ minitest (~> 4.2)
14
+ multi_json (~> 1.3)
15
+ thread_safe (~> 0.1)
16
+ tzinfo (~> 0.3.37)
17
+ atomic (1.1.14)
18
+ diff-lcs (1.2.5)
19
+ i18n (0.6.5)
20
+ minitest (4.7.5)
21
+ multi_json (1.8.2)
22
+ rake (10.1.0)
23
+ redis (3.0.5)
24
+ rspec (2.14.1)
25
+ rspec-core (~> 2.14.0)
26
+ rspec-expectations (~> 2.14.0)
27
+ rspec-mocks (~> 2.14.0)
28
+ rspec-core (2.14.7)
29
+ rspec-expectations (2.14.4)
30
+ diff-lcs (>= 1.1.3, < 2.0)
31
+ rspec-mocks (2.14.4)
32
+ thread_safe (0.1.3)
33
+ atomic
34
+ tzinfo (0.3.38)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ bundler (~> 1.3)
41
+ rake
42
+ redis (~> 3.0)
43
+ redis-copy!
44
+ rspec (~> 2.14)
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+ source 'https://rubygems.org'
3
+
4
+ gem 'redis', :git => 'https://github.com/redis/redis-rb.git'
5
+
6
+ gemspec :path => '../'
@@ -0,0 +1,49 @@
1
+ GIT
2
+ remote: https://github.com/redis/redis-rb.git
3
+ revision: 9bb4156dcd43105b68e24e5efc579dddb12cf260
4
+ specs:
5
+ redis (3.0.6)
6
+
7
+ PATH
8
+ remote: ../
9
+ specs:
10
+ redis-copy (0.0.5)
11
+ activesupport
12
+ redis
13
+
14
+ GEM
15
+ remote: https://rubygems.org/
16
+ specs:
17
+ activesupport (4.0.1)
18
+ i18n (~> 0.6, >= 0.6.4)
19
+ minitest (~> 4.2)
20
+ multi_json (~> 1.3)
21
+ thread_safe (~> 0.1)
22
+ tzinfo (~> 0.3.37)
23
+ atomic (1.1.14)
24
+ diff-lcs (1.2.5)
25
+ i18n (0.6.5)
26
+ minitest (4.7.5)
27
+ multi_json (1.8.2)
28
+ rake (10.1.0)
29
+ rspec (2.14.1)
30
+ rspec-core (~> 2.14.0)
31
+ rspec-expectations (~> 2.14.0)
32
+ rspec-mocks (~> 2.14.0)
33
+ rspec-core (2.14.7)
34
+ rspec-expectations (2.14.4)
35
+ diff-lcs (>= 1.1.3, < 2.0)
36
+ rspec-mocks (2.14.4)
37
+ thread_safe (0.1.3)
38
+ atomic
39
+ tzinfo (0.3.38)
40
+
41
+ PLATFORMS
42
+ ruby
43
+
44
+ DEPENDENCIES
45
+ bundler (~> 1.3)
46
+ rake
47
+ redis!
48
+ redis-copy!
49
+ rspec (~> 2.14)
data/README.md CHANGED
@@ -24,7 +24,7 @@ $ redis-copy --help
24
24
  redis-copy v0.0.5
25
25
  Usage: redis-copy [options] <source> <destination>
26
26
  <source> and <destination> must be redis connection uris
27
- like [redis://]<hostname>[:<port>][/<db>]
27
+ like [redis://][<username>:<password>@]<hostname>[:<port>][/<db>]
28
28
 
29
29
  Specific options:
30
30
  --strategy STRATEGY Select strategy (auto, new, classic) (default auto)
data/Rakefile CHANGED
@@ -7,3 +7,63 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
7
7
  spec.pattern = FileList['spec/**/*_spec.rb']
8
8
  spec.verbose = true
9
9
  end
10
+
11
+
12
+ # STOLEN NEARLY VERBATIM FROM MIT-LICENSED REDIS-RB
13
+ # https://github.com/redis/redis-rb
14
+ require 'rubygems'
15
+
16
+ ENV["REDIS_BRANCH"] ||= "unstable"
17
+
18
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
19
+ require 'redis/version'
20
+
21
+ REDIS_DIR = File.expand_path(File.join("..", "spec"), __FILE__)
22
+ REDIS_CNF = File.join(REDIS_DIR, "redis.spec.conf")
23
+ REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
24
+ BINARY = "tmp/redis-#{ENV["REDIS_BRANCH"]}/src/redis-server"
25
+
26
+ task :default => :run
27
+
28
+ desc "Run tests and manage server start/stop"
29
+ task :run => [:start, :spec, :stop]
30
+
31
+ desc "Start the Redis server"
32
+ task :start => BINARY do
33
+ sh "#{BINARY} --version"
34
+
35
+ redis_running = \
36
+ begin
37
+ File.exists?(REDIS_PID) && Process.kill(0, File.read(REDIS_PID).to_i)
38
+ rescue Errno::ESRCH
39
+ FileUtils.rm REDIS_PID
40
+ false
41
+ end
42
+
43
+ unless redis_running
44
+ unless system("#{BINARY} #{REDIS_CNF}")
45
+ abort "could not start redis-server"
46
+ end
47
+ end
48
+ end
49
+
50
+ desc "Stop the Redis server"
51
+ task :stop do
52
+ if File.exists?(REDIS_PID)
53
+ Process.kill "INT", File.read(REDIS_PID).to_i
54
+ FileUtils.rm REDIS_PID
55
+ end
56
+ end
57
+
58
+ file BINARY do
59
+ branch = ENV.fetch("REDIS_BRANCH")
60
+
61
+ sh <<-SH
62
+ mkdir -p tmp;
63
+ cd tmp;
64
+ wget https://github.com/antirez/redis/archive/#{branch}.tar.gz -O #{branch}.tar.gz;
65
+ tar xf #{branch}.tar.gz;
66
+ cd redis-#{branch};
67
+ make
68
+ SH
69
+ end
@@ -93,8 +93,13 @@ module RedisCopy
93
93
  host = uri.host
94
94
  port = uri.port if uri.port
95
95
  db = uri.path ? uri.path[1..-1].to_i : 0
96
+ password = uri.password
96
97
 
97
- Redis.new(host: host, port: port, db: db)
98
+ # Connect & Ping to ensure access.
99
+ Redis.new(host: host, port: port, db: db, password: password).tap(&:ping)
100
+ rescue Redis::CommandError => e
101
+ fail(Redis::CommandError,
102
+ "There was a problem connecting to #{uri.to_s}\n#{e.message}")
98
103
  end
99
104
  end
100
105
  end
@@ -5,10 +5,10 @@ require 'optparse'
5
5
 
6
6
  module RedisCopy
7
7
  class CLI
8
- REDIS_URI = (/\A(?:redis:\/\/)?([a-z0-9\-.]+)(:[0-9]{1,5})?(\/(?:(?:1[0-5])|[0-9]))?\z/i).freeze
8
+ REDIS_URI = (/\A(?:redis:\/\/)?(\w*:\w+@)?([a-z0-9\-.]+)(:[0-9]{1,5})?(\/(?:(?:1[0-5])|[0-9]))?\z/i).freeze
9
9
  DEFAULTS = {
10
10
  ui: :command_line,
11
- key_emitter: :default,
11
+ key_emitter: :auto,
12
12
  strategy: :auto,
13
13
  verify: 0,
14
14
  pipeline: :true,
@@ -25,7 +25,7 @@ module RedisCopy
25
25
 
26
26
  OptionParser.new do |opts|
27
27
  opts.version = RedisCopy::VERSION
28
- opts.banner = "#{opts.program_name} v#{opts.version}\n" +
28
+ opts.banner = "#{opts.program_name} v#{opts.version} (with redis-rb #{Redis::VERSION})\n" +
29
29
  "Usage: #{opts.program_name} [options] <source> <destination>"
30
30
 
31
31
  indent_desc = proc do |desc|
@@ -33,7 +33,7 @@ module RedisCopy
33
33
  end
34
34
 
35
35
  opts.separator " <source> and <destination> must be redis connection uris"
36
- opts.separator " like [redis://]<hostname>[:<port>][/<db>]"
36
+ opts.separator " like [redis://][<username>:<password>@]<hostname>[:<port>][/<db>]"
37
37
  opts.separator ''
38
38
  opts.separator "Specific options:"
39
39
 
@@ -48,6 +48,17 @@ module RedisCopy
48
48
  options[:strategy] = strategy
49
49
  end
50
50
 
51
+ opts.on('--emitter EMITTER', [:auto, :scan, :keys],
52
+ indent_desc.(
53
+ "Select key emitter (auto, keys, scan) (default #{DEFAULTS[:strategy]})\n" +
54
+ " auto: uses scan if available, otherwise fallback\n" +
55
+ " scan: use redis SCAN command (faster, less blocking)\n" +
56
+ " keys: uses redis KEYS command (dangerous, esp. on large datasets)"
57
+ )
58
+ ) do |emitter|
59
+ options[:key_emitter] = emitter
60
+ end
61
+
51
62
  opts.on('--[no-]pipeline',
52
63
  "Use redis pipeline where available (default #{DEFAULTS[:pipeline]})"
53
64
  ) do |pipeline|
@@ -9,9 +9,15 @@ module RedisCopy
9
9
  module KeyEmitter
10
10
  def self.load(redis, ui, options = {})
11
11
  key_emitter = options.fetch(:key_emitter, :default)
12
- const_name = key_emitter.to_s.camelize
13
- require "redis-copy/key-emitter/#{key_emitter}" unless const_defined?(const_name)
14
- const_get(const_name).new(redis, ui, options)
12
+ scan_compatible = Scan::compatible?(redis)
13
+ emitklass = case key_emitter
14
+ when :keys then Keys
15
+ when :scan
16
+ raise ArgumentError unless scan_compatible
17
+ Scan
18
+ when :auto then scan_compatible ? Scan : Keys
19
+ end
20
+ emitklass.new(redis, ui, options)
15
21
  end
16
22
 
17
23
  # @param redis [Redis]
@@ -37,7 +43,7 @@ module RedisCopy
37
43
  end
38
44
 
39
45
  # The default strategy blindly uses `redis.keys('*')`
40
- class Default
46
+ class Keys
41
47
  include KeyEmitter
42
48
 
43
49
  def keys
@@ -66,6 +72,27 @@ module RedisCopy
66
72
  @ui.debug "REDIS: #{@redis.client.id} KEYS *"
67
73
  @redis.keys('*').to_enum
68
74
  end
75
+
76
+ def self.compatible?(redis)
77
+ true
78
+ end
79
+ end
80
+
81
+ class Scan
82
+ include KeyEmitter
83
+
84
+ def keys
85
+ @redis.scan_each(count: 1000)
86
+ end
87
+
88
+ def self.compatible?(redis)
89
+ bin_version = Gem::Version.new(redis.info['redis_version'])
90
+ bin_requirement = Gem::Requirement.new('>= 2.7.105')
91
+
92
+ return false unless bin_requirement.satisfied_by?(bin_version)
93
+
94
+ redis.respond_to?(:scan_each)
95
+ end
69
96
  end
70
97
  end
71
98
  end
@@ -1,5 +1,36 @@
1
1
  # encoding: utf-8
2
2
 
3
+ # The Classic strategy borrows *heavily* from the utils/redis-copy.rb found
4
+ # in Redis source, which is also the inspiration for this gem.
5
+ #
6
+ # #{REDIS}/utils/redis-copy.rb - Copyright © 2009-2010 Salvatore Sanfilippo.
7
+ # All rights reserved.
8
+ #
9
+ # Redistribution and use in source and binary forms, with or without
10
+ # modification, are permitted provided that the following conditions
11
+ # are met:
12
+ #
13
+ # * Redistributions of source code must retain the above copyright
14
+ # notice, this list of conditions and the following disclaimer.
15
+ # * Redistributions in binary form must reproduce the above copyright
16
+ # notice, this list of conditions and the following disclaimer in
17
+ # the documentation and/or other materials provided with the
18
+ # distribution.
19
+ # * Neither the name of Redis nor the names of its contributors may
20
+ # be used to endorse or promote products derived from this software
21
+ # without specific prior written permission.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
27
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
+ # POSSIBILITY OF SUCH DAMAGE.
3
34
  module RedisCopy
4
35
  module Strategy
5
36
  class Classic
@@ -51,7 +82,11 @@ module RedisCopy
51
82
  vs_zset = @src.zrange(key, 0, -1, :with_scores => true)
52
83
  sv_zset = vs_zset.map(&:reverse)
53
84
  @dst.zadd(key, sv_zset)
85
+ when 'none'
86
+ @ui.warn("GONE: #{key.dump}")
87
+ return false
54
88
  else
89
+ @ui.warn("UNKNOWN(#{vtype}): #{key.dump}")
55
90
  return false
56
91
  end
57
92
 
@@ -71,6 +106,10 @@ module RedisCopy
71
106
  def pipeline_enabled?
72
107
  @pipeline_enabled ||= (false | @opt[:pipeline])
73
108
  end
109
+
110
+ def self.compatible?(redis)
111
+ true
112
+ end
74
113
  end
75
114
  end
76
115
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module RedisCopy
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.6'
5
5
  end
@@ -8,13 +8,12 @@ Gem::Specification.new do |spec|
8
8
  spec.name = 'redis-copy'
9
9
  spec.version = RedisCopy::VERSION
10
10
 
11
- authors_and_emails = [['Ryan Biesemeyer', 'ryan@yaauie.com']]
12
- # authors_and_emails = (`sh git shortlog -sne`).lines.map do |l|
13
- # (/(?<=\t)(.+) <(.+)>\z/).match(l.chomp).last(2)
14
- # end.compact.map(&:to_a)
11
+ authors_and_emails = (`git shortlog -sne`).lines.map do |l|
12
+ (/(?<=\t)(.+) <(.+)>\z/).match(l.chomp).to_a.last(2)
13
+ end.compact.map(&:to_a)
15
14
 
16
15
  spec.authors = authors_and_emails.map(&:first)
17
- spec.email = authors_and_emails.map(&:last)
16
+ spec.email = ['redis-copy@yaauie.com']
18
17
  spec.summary = 'Copy the contents of one redis db to another'
19
18
  spec.description = 'A command-line utility built for copying the ' +
20
19
  'contents of one redis db to another over a ' +
@@ -1,51 +1,67 @@
1
1
  # encoding: utf-8
2
2
  require 'redis-copy'
3
+ require_relative '../spec_helper.rb'
3
4
 
4
- describe RedisCopy::KeyEmitter::Default do
5
- let(:redis) { double }
5
+ shared_examples_for RedisCopy::KeyEmitter do
6
+ let(:emitter_klass) { described_class }
7
+ let(:redis) { Redis.new(REDIS_OPTIONS) }
6
8
  let(:ui) { double.as_null_object }
7
- let(:instance) { RedisCopy::KeyEmitter::Default.new(redis, ui)}
8
- let(:connection_uri) { 'redis://12.34.56.78:9000/15' }
9
- let(:key_count) { 100_000 }
9
+ let(:instance) { emitter_klass.new(redis, ui)}
10
+ let(:key_count) { 1 }
11
+ let(:keys) { key_count.times.map{|i| i.to_s(16) } }
10
12
 
11
13
  before(:each) do
12
- redis.stub_chain('client.id').and_return(connection_uri)
13
- redis.stub(:dbsize) { key_count }
14
+ unless emitter_klass.compatible?(redis)
15
+ pending "#{emitter_klass} not supported in your environment"
16
+ end
17
+ key_count.times.each_slice(50) do |keys|
18
+ kv = keys.map{|x| x.to_s(16)}.zip(keys)
19
+ redis.mset(*kv.flatten)
20
+ end
14
21
  ui.stub(:debug).with(anything)
15
22
  end
23
+ after(:each) { redis.flushdb }
16
24
 
17
25
  context '#keys' do
18
- let(:mock_return) { ['foo:bar', 'asdf:qwer'] }
19
- before(:each) do
20
- redis.should_receive(:keys).with('*').exactly(:once).and_return(mock_return)
21
- end
26
+ let(:key_count) { 64 }
22
27
  context 'the result' do
23
28
  subject { instance.keys }
24
- its(:to_a) { should eq mock_return }
29
+ its(:to_a) { should =~ keys }
25
30
  end
26
- context 'the supplied ui' do
27
- it 'should get a debug message' do
28
- ui.should_receive(:debug).
29
- with(/#{Regexp.escape(connection_uri)} KEYS \*/).
30
- exactly(:once)
31
- instance.keys
32
- end
33
- context 'when source has > 10,000 keys' do
34
- let(:key_count) { 100_000 }
35
- it 'should ask for confirmation' do
36
- ui.should_receive(:confirm?) do |confirmation|
37
- confirmation.should match /\b100,000\b/
38
- end
31
+ end
32
+ end
33
+
34
+ describe RedisCopy::KeyEmitter::Keys do
35
+ it_should_behave_like RedisCopy::KeyEmitter do
36
+ context '#keys' do
37
+ context 'the supplied ui' do
38
+ it 'should get a debug message' do
39
+ ui.should_receive(:debug).
40
+ with(/#{redis.client.id} KEYS \*/).
41
+ exactly(:once)
39
42
  instance.keys
40
43
  end
41
- end
42
- context 'when source has <= 10,000 keys' do
43
- let(:key_count) { 1_000 }
44
- it 'should not ask for confirmation' do
45
- ui.should_not_receive(:confirm?)
46
- instance.keys
44
+ context 'when source has > 10,000 keys' do
45
+ let(:key_count) { 10_001 }
46
+ it 'should ask for confirmation' do
47
+ ui.should_receive(:confirm?) do |confirmation|
48
+ confirmation.should match /\b10,001/
49
+ end
50
+ instance.keys
51
+ end
52
+ end
53
+ context 'when source has <= 10,000 keys' do
54
+ let(:key_count) { 1_000 }
55
+ it 'should not ask for confirmation' do
56
+ ui.should_not_receive(:confirm?)
57
+ instance.keys
58
+ end
47
59
  end
48
60
  end
49
61
  end
50
62
  end
51
63
  end
64
+
65
+ describe RedisCopy::KeyEmitter::Scan do
66
+ it_should_behave_like RedisCopy::KeyEmitter
67
+ end
@@ -1,40 +1,10 @@
1
1
  # encoding: utf-8
2
- class RedisMultiplex < Struct.new(:source, :destination)
3
- ResponseError = Class.new(RuntimeError)
4
-
5
- def ensure_same!(&blk)
6
- responses = {
7
- source: capture_result(source, &blk),
8
- destination: capture_result(destination, &blk)
9
- }
10
- unless responses[:source] == responses[:destination]
11
- raise ResponseError.new(responses.to_s)
12
- end
13
- case responses[:destination].first
14
- when :raised then raise responses[:destination].last
15
- when :returned then return responses[:destination].last
16
- end
17
- end
18
- alias_method :both!, :ensure_same!
19
-
20
- def both(&blk)
21
- both!(&blk)
22
- true
23
- rescue ResponseError
24
- false
25
- end
26
-
27
- def capture_result(redis, &block)
28
- return [:returned, block.call(redis)]
29
- rescue Object => exception
30
- return [:raised, exception]
31
- end
32
- end
2
+ require_relative '../spec_helper'
33
3
 
34
4
  shared_examples_for(:no_ttl) do
35
5
  # key, redis,
36
6
  subject { redis.ttl(key) }
37
- it { should eq -1 }
7
+ it { should be < 0 }
38
8
  end
39
9
 
40
10
  shared_examples_for(:ttl_set) do
@@ -56,10 +26,25 @@ shared_examples_for '#verify?' do
56
26
  end
57
27
 
58
28
  shared_examples_for(RedisCopy::Strategy) do
29
+ let(:strategy_class) { described_class }
30
+ let(:options) { Hash.new } # append using before(:each) { options.update(foo: true) }
31
+ # let(:ui) { double.as_null_object }
32
+ let(:ui) { RedisCopy::UI::CommandLine.new(options) }
33
+ let(:strategy) { strategy_class.new(source, destination, ui, options)}
34
+ let(:multiplex) { RedisMultiplex.new(source, destination) }
35
+ let(:source) { Redis.new(REDIS_OPTIONS.merge(db: 14)) }
36
+ let(:destination) { Redis.new(REDIS_OPTIONS.merge(db: 15)) }
37
+
59
38
  let(:key) { rand(16**128).to_s(16) }
60
39
  after(:each) { multiplex.both { |redis| redis.del(key) } }
61
40
  let(:ttl) { 100 }
62
41
 
42
+ before(:each) do
43
+ unless [source, destination].all?{|redis| strategy_class.compatible?(redis) }
44
+ pending "#{strategy_class} not supported in your environment"
45
+ end
46
+ end
47
+
63
48
  context '#copy' do
64
49
  before(:each) { populate.call }
65
50
  context 'string' do
@@ -301,22 +286,13 @@ shared_examples_for(RedisCopy::Strategy) do
301
286
  end
302
287
  end
303
288
 
304
- describe RedisCopy::Strategy do
305
- let(:options) { Hash.new } # append using before(:each) { options.update(foo: true) }
306
- # let(:ui) { double.as_null_object }
307
- let(:ui) { RedisCopy::UI::CommandLine.new(options) }
308
- let(:strategy) { strategy_class.new(source, destination, ui, options)}
309
- let(:multiplex) { RedisMultiplex.new(source, destination) }
310
- let(:source) { Redis.new(db: 14) }
311
- let(:destination) { Redis.new(db: 15) }
312
289
 
313
- describe :New do
314
- let(:strategy_class) { RedisCopy::Strategy::New }
315
- it_should_behave_like RedisCopy::Strategy
316
- end
317
- describe :Classic do
318
- let(:strategy_class) { RedisCopy::Strategy::Classic }
319
- it_should_behave_like RedisCopy::Strategy
290
+ describe RedisCopy::Strategy::New do
291
+ it_should_behave_like RedisCopy::Strategy
292
+ end
293
+
294
+ describe RedisCopy::Strategy::Classic do
295
+ it_should_behave_like RedisCopy::Strategy do
320
296
  context '#maybe_pipeline' do
321
297
  it 'should not pipeline' do
322
298
  source.should_not_receive(:pipelined)
@@ -0,0 +1,9 @@
1
+ dir ./spec/db
2
+ pidfile ./redis.pid
3
+ port 6381
4
+ unixsocket ./redis.sock
5
+ timeout 300
6
+ loglevel debug
7
+ logfile stdout
8
+ databases 16
9
+ daemonize yes
@@ -0,0 +1,38 @@
1
+ REDIS_PORT = 6381
2
+ REDIS_OPTIONS = {
3
+ :port => REDIS_PORT,
4
+ :db => 14,
5
+ :timeout => Float(ENV["TIMEOUT"] || 0.1),
6
+ }
7
+
8
+ class RedisMultiplex < Struct.new(:source, :destination)
9
+ ResponseError = Class.new(RuntimeError)
10
+
11
+ def ensure_same!(&blk)
12
+ responses = {
13
+ source: capture_result(source, &blk),
14
+ destination: capture_result(destination, &blk)
15
+ }
16
+ unless responses[:source] == responses[:destination]
17
+ raise ResponseError.new(responses.to_s)
18
+ end
19
+ case responses[:destination].first
20
+ when :raised then raise responses[:destination].last
21
+ when :returned then return responses[:destination].last
22
+ end
23
+ end
24
+ alias_method :both!, :ensure_same!
25
+
26
+ def both(&blk)
27
+ both!(&blk)
28
+ true
29
+ rescue ResponseError
30
+ false
31
+ end
32
+
33
+ def capture_result(redis, &block)
34
+ return [:returned, block.call(redis)]
35
+ rescue Object => exception
36
+ return [:raised, exception]
37
+ end
38
+ end
metadata CHANGED
@@ -1,15 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-copy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Ryan Biesemeyer
9
+ - Denis Goeury
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2013-11-01 00:00:00.000000000 Z
13
+ date: 2013-11-13 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: bundler
@@ -95,13 +96,18 @@ description: A command-line utility built for copying the contents of one redis
95
96
  to another over a network. Supports all data types, persists ttls, and attempts
96
97
  to be as efficient as possible.
97
98
  email:
98
- - ryan@yaauie.com
99
+ - redis-copy@yaauie.com
99
100
  executables:
100
101
  - redis-copy
101
102
  extensions: []
102
103
  extra_rdoc_files: []
103
104
  files:
104
105
  - .gitignore
106
+ - .travis.yml
107
+ - .travis/Gemfile.redis-gem-3.0
108
+ - .travis/Gemfile.redis-gem-3.0.lock
109
+ - .travis/Gemfile.redis-gem-master
110
+ - .travis/Gemfile.redis-gem-master.lock
105
111
  - Gemfile
106
112
  - LICENSE.txt
107
113
  - README.md
@@ -120,8 +126,11 @@ files:
120
126
  - lib/redis-copy/version.rb
121
127
  - redis-copy.gemspec
122
128
  - redis-copy_spec.rb
129
+ - spec/db/.gitkeep
123
130
  - spec/redis-copy/key-emitter_spec.rb
124
131
  - spec/redis-copy/strategy_spec.rb
132
+ - spec/redis.spec.conf
133
+ - spec/spec_helper.rb
125
134
  homepage: https://github.com/yaauie/redis-copy
126
135
  licenses:
127
136
  - MIT
@@ -135,12 +144,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
135
144
  - - ! '>='
136
145
  - !ruby/object:Gem::Version
137
146
  version: '0'
147
+ segments:
148
+ - 0
149
+ hash: 2506344141455451823
138
150
  required_rubygems_version: !ruby/object:Gem::Requirement
139
151
  none: false
140
152
  requirements:
141
153
  - - ! '>='
142
154
  - !ruby/object:Gem::Version
143
155
  version: '0'
156
+ segments:
157
+ - 0
158
+ hash: 2506344141455451823
144
159
  requirements: []
145
160
  rubyforge_project:
146
161
  rubygems_version: 1.8.24
@@ -148,6 +163,8 @@ signing_key:
148
163
  specification_version: 3
149
164
  summary: Copy the contents of one redis db to another
150
165
  test_files:
166
+ - spec/db/.gitkeep
151
167
  - spec/redis-copy/key-emitter_spec.rb
152
168
  - spec/redis-copy/strategy_spec.rb
153
- has_rdoc:
169
+ - spec/redis.spec.conf
170
+ - spec/spec_helper.rb