has_versions 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +1 -37
  2. data/Gemfile.lock +17 -45
  3. data/Rakefile +16 -16
  4. data/has_versions.gemspec +6 -6
  5. data/lib/has_versions/apply/simple.rb +1 -1
  6. data/lib/has_versions/attributes.rb +2 -26
  7. data/lib/has_versions/configuration.rb +3 -16
  8. data/lib/has_versions/diff/simple.rb +1 -1
  9. data/lib/has_versions/merge/always_conflicted.rb +9 -0
  10. data/lib/has_versions/merge/base.rb +22 -0
  11. data/lib/has_versions/merge/choose_first.rb +9 -0
  12. data/lib/has_versions/merge/diff3.rb +66 -0
  13. data/lib/has_versions/merge/fast_forward.rb +34 -0
  14. data/lib/has_versions/merge/merge_base.rb +75 -0
  15. data/lib/has_versions/merge/octopus.rb +42 -0
  16. data/lib/has_versions/merge/three_way.rb +53 -0
  17. data/lib/has_versions/merge.rb +16 -45
  18. data/lib/has_versions/orm/cassandra_object.rb +15 -0
  19. data/lib/has_versions/reset/simple.rb +2 -2
  20. data/lib/has_versions/version.rb +1 -1
  21. data/lib/has_versions/versioned.rb +1 -5
  22. data/lib/has_versions/versioning_key.rb +0 -1
  23. data/lib/has_versions.rb +10 -0
  24. data/spec/has_versions/apply_spec.rb +5 -5
  25. data/spec/has_versions/configuration_spec.rb +7 -7
  26. data/spec/has_versions/diff_spec.rb +4 -4
  27. data/spec/has_versions/merge/diff3_spec.rb +29 -0
  28. data/spec/has_versions/merge/merge_base_spec.rb +61 -0
  29. data/spec/has_versions/merge/octopus_spec.rb +74 -0
  30. data/spec/has_versions/merge/three_way_spec.rb +57 -0
  31. data/spec/has_versions/reset_spec.rb +1 -2
  32. data/spec/has_versions/stage_spec.rb +14 -15
  33. data/spec/has_versions/versioned_spec.rb +4 -4
  34. data/spec/has_versions/versioning_key_spec.rb +1 -1
  35. data/spec/spec_helper.rb +7 -8
  36. data/spec/support/matchers/be_a_uuid.rb +0 -1
  37. data/spec/support/version.rb +10 -0
  38. metadata +34 -69
  39. data/lib/has_versions/merge/stupid.rb +0 -44
  40. data/lib/has_versions/stage.rb +0 -121
  41. data/spec/has_versions/merge_spec.rb +0 -67
  42. data/spec/support/matchers/have_key.rb +0 -25
@@ -7,8 +7,8 @@ module HasVersions
7
7
 
8
8
  module Simple
9
9
  def reset!(version)
10
- versioning_configuration.attributes.each do |name, options|
11
- value = versioning_decode_value(version.snapshot[name], options)
10
+ versioning_configuration.attributes.each do |name|
11
+ value = versioning_decode_value(name, version.snapshot[name])
12
12
  __send__("#{name}=", value)
13
13
  end
14
14
  self
@@ -1,4 +1,4 @@
1
1
  module HasVersions
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
4
4
 
@@ -1,5 +1,4 @@
1
1
  module HasVersions
2
-
3
2
  module Versioned
4
3
  extend ActiveSupport::Concern
5
4
 
@@ -12,7 +11,6 @@ module HasVersions
12
11
 
13
12
  module ClassMethods
14
13
  def has_versions(version_class, &block)
15
-
16
14
  self.versioning_configuration = HasVersions::Configuration.new(&block)
17
15
  self.version_class = version_class
18
16
  end
@@ -24,11 +22,9 @@ module HasVersions
24
22
 
25
23
  # generates a version from the current object
26
24
  def to_version
27
- self.class.version_class.new.tap do |version|
25
+ version_class.new.tap do |version|
28
26
  version.snapshot = versioned_attributes
29
27
  end
30
28
  end
31
-
32
29
  end
33
-
34
30
  end
@@ -5,4 +5,3 @@ module HasVersions
5
5
  autoload :UUID
6
6
  end
7
7
  end
8
-
data/lib/has_versions.rb CHANGED
@@ -17,5 +17,15 @@ module HasVersions
17
17
  autoload :Reset
18
18
  autoload :VersioningKey
19
19
 
20
+ module Orm
21
+ extend ActiveSupport::Autoload
22
+
23
+ autoload :CassandraObject
24
+ end
25
+
20
26
  class VersioningError < StandardError; end
21
27
  end
28
+
29
+ ActiveSupport.on_load :cassandra_object do
30
+ include HasVersions::Orm::CassandraObject
31
+ end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  class ApplyingVersion < Version; end
4
4
 
@@ -15,7 +15,7 @@ describe ApplyingVersion do
15
15
  let (:patch3) { {name: ['foo2', 'foo3']}.with_indifferent_access }
16
16
 
17
17
  it 'should patch the snapshot' do
18
- foo1.apply(patch1).snapshot.should have_key(:name).with_value('foo2')
18
+ foo1.apply(patch1).snapshot.should include(name: 'foo2')
19
19
  end
20
20
 
21
21
  it 'should raise on non-applying patch' do
@@ -23,11 +23,11 @@ describe ApplyingVersion do
23
23
  end
24
24
 
25
25
  it 'should apply patches adding attributes', :focus => true do
26
- foo2.apply(patch2).snapshot.should have_key(:name).with_value('foo1')
27
- foo2.apply(patch2).snapshot.should have_key(:stripey).with_value('socks')
26
+ foo2.apply(patch2).snapshot.should include(name: 'foo1')
27
+ foo2.apply(patch2).snapshot.should include(stripey: 'socks')
28
28
  end
29
29
 
30
30
  it 'should apply multiple patches' do
31
- foo1.apply(patch1, patch3).snapshot.should have_key(:name).with_value('foo3')
31
+ foo1.apply(patch1, patch3).snapshot.should include(name: 'foo3')
32
32
  end
33
33
  end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe 'Configuration' do
4
4
  subject do
@@ -11,23 +11,23 @@ describe 'Configuration' do
11
11
  end
12
12
 
13
13
  it 'should include specified attributes' do
14
- subject.attributes.should have_key(:foo)
15
- subject.attributes.should have_key(:bar)
14
+ subject.attributes.should include(:foo)
15
+ subject.attributes.should include(:bar)
16
16
  end
17
17
 
18
18
  it 'should set options on attributes' do
19
- subject.attributes[:baz].should have_key(:type).with_value(:baz)
19
+ subject.attributes[:baz].should include(type: :baz)
20
20
  end
21
21
 
22
22
  it 'should allow indifferent access to attributes' do
23
- subject.attributes.should have_key("foo")
23
+ subject.attributes.should include("foo")
24
24
  end
25
25
 
26
26
  it 'should include specified types' do
27
- subject.types.should have_key(:baz)
27
+ subject.types.should include(:baz)
28
28
  end
29
29
 
30
30
  it 'should propagate type options to typed attributes' do
31
- subject.attributes[:baz].should have_key(:expected).with_value(Hash)
31
+ subject.attributes[:baz].should include(expected: Hash)
32
32
  end
33
33
  end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  class DiffingVersion < Version; end
4
4
 
@@ -15,14 +15,14 @@ describe DiffingVersion do
15
15
  end
16
16
 
17
17
  it 'should return a patch when diffed with another version' do
18
- foo.diff(foo2).should have_key(:name).with_value(['foo', 'foo2'])
18
+ foo.diff(foo2).should include(name: ['foo', 'foo2'])
19
19
  end
20
20
 
21
21
  it 'should include patches for attributes added' do
22
- foo.diff(foo2).should have_key(:stripey).with_value([nil, 'socks'])
22
+ foo.diff(foo2).should include(stripey: [nil, 'socks'])
23
23
  end
24
24
 
25
25
  it 'should include patches for attributes deleted' do
26
- foo.diff(foo2).should have_key(:argyle).with_value(['socks', nil])
26
+ foo.diff(foo2).should include(argyle: ['socks', nil])
27
27
  end
28
28
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Merge" do
4
+ describe "Diff3" do
5
+
6
+ it 'should behave like a 3-way merge' do
7
+ extend HasVersions::Merge::Diff3
8
+
9
+ diff3('A','A','A').should == [true, 'A']
10
+ diff3('A','B','A').should == [true, 'B']
11
+ diff3('A','A','B').should == [true, 'B']
12
+ diff3('B','A','A').should == [true, 'A']
13
+ diff3('B','A','C').should == [false, ['A', 'C']]
14
+ end
15
+
16
+ it 'should take multiple theirs' do
17
+ extend HasVersions::Merge::Diff3
18
+
19
+ diff3('A','A',*['A','A']).should == [true, 'A']
20
+ diff3('A','A',*['B','B']).should == [true, 'B']
21
+ diff3('A','A',*['B','B']).should == [true, 'B']
22
+ diff3('A','A',*['A','B']).should == [false, ['A','B']]
23
+ diff3('A','A',*['B','C']).should == [false, ['B','C']]
24
+ diff3('A','B',*['C','D']).should == [false, ['C','D','B']]
25
+ end
26
+
27
+ it 'should handle _missing values'
28
+ end
29
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Merge" do
4
+ describe "MergeBase" do
5
+ include HasVersions::Merge::MergeBase
6
+
7
+ it 'should find no bases for one version' do
8
+ v = MergingVersion.new
9
+ merge_bases([v]).should == []
10
+ end
11
+
12
+ it 'should find merge base for a version and its parent' do
13
+ p1 = MergingVersion.new
14
+ v1 = MergingVersion.new
15
+ v1.parents = p1
16
+ merge_bases([v1, p1]).should == [p1]
17
+ end
18
+
19
+ it 'should find merge base for versions with multiple ancestors' do
20
+ p1 = MergingVersion.new
21
+ p2 = MergingVersion.new
22
+ v1 = MergingVersion.new
23
+ v1.parents = p1
24
+ p1.parents = p2
25
+ merge_bases([v1, p1]).should == [p1]
26
+ end
27
+
28
+ it 'should find merge base for two versions with the same parent' do
29
+ p1 = MergingVersion.new
30
+ v1 = MergingVersion.new
31
+ v2 = MergingVersion.new
32
+ v1.parents = v2.parents = [p1]
33
+ merge_bases([v1, v2]).should == [p1]
34
+ end
35
+
36
+ it 'should find merge base for multiple versions with different parents' do
37
+ p1, p2, v1, v2, v3 = *5.times.collect { MergingVersion.new }
38
+ p2.parents = [p1]
39
+ v1.parents = [p1]
40
+ v2.parents = [p2]
41
+ v3.parents = [v2]
42
+ merge_bases([v1, v2, v3]).should == [p1]
43
+ end
44
+
45
+ it 'should find no base for versions with no common ancestry' do
46
+ v1 = MergingVersion.new
47
+ v2 = MergingVersion.new
48
+ merge_bases([v1, v2]).should == []
49
+ end
50
+
51
+ #TODO this is broken
52
+ it 'should find multiple bases for versions with criss-cross ancestry' do
53
+ p1, p2, p3, v1, v2 = *5.times.collect { MergingVersion.new }
54
+ p2.parents = [p1]
55
+ p3.parents = [p1]
56
+ v1.parents = [p3, p2]
57
+ v2.parents = [p2, p3]
58
+ merge_bases([v1, v2]).should == [p2, p3]
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe HasVersions::Merge::Octopus do
4
+
5
+ it 'should only accept 3+ versions' do
6
+ expect { merge([MergingVersion.new]) }.to raise_error(HasVersions::MergeFailed)
7
+ expect { merge([MergingVersion.new]*2) }.to raise_error(HasVersions::MergeFailed)
8
+ end
9
+
10
+ it 'should merge identical versions' do
11
+ v1 = MergingVersion.new(name: 'foo1')
12
+ v2 = MergingVersion.new(name: 'foo1')
13
+ v3 = MergingVersion.new(name: 'foo1')
14
+ merge([v1, v2, v3]).resolution.snapshot.should include(name: 'foo1')
15
+ end
16
+
17
+ it 'should merge many versions' do
18
+ v1 = MergingVersion.new(name: 'foo1')
19
+ v2 = MergingVersion.new(name: 'foo1')
20
+ v3 = MergingVersion.new(name: 'foo1')
21
+ v4 = MergingVersion.new(name: 'foo1')
22
+ v5 = MergingVersion.new(name: 'foo1')
23
+ v6 = MergingVersion.new(name: 'foo1')
24
+ merge([v1, v2, v3, v4, v5, v6]).resolution.snapshot.should include(name: 'foo1')
25
+ end
26
+
27
+ it 'should detect conflicts' do
28
+ v1 = MergingVersion.new(name: 'foo1')
29
+ v2 = MergingVersion.new(name: 'foo2')
30
+ v3 = MergingVersion.new(name: 'foo3')
31
+ merge([v1, v2, v3]).conflicts.should include(name: ['foo1', 'foo2', 'foo3'])
32
+ end
33
+
34
+ it 'should merge ancestors cleanly' do
35
+ v1 = MergingVersion.new(name: 'foo1')
36
+ v2 = MergingVersion.new(name: 'foo2')
37
+ v3 = MergingVersion.new(name: 'foo3')
38
+
39
+ v3.parents = v2
40
+ v2.parents = v1
41
+ merge([v1, v2, v3]).conflicts.should be_empty
42
+ merge([v1, v2, v3]).resolution.snapshot.should include(name: 'foo3')
43
+ end
44
+
45
+ it 'should merge missing attributes' do
46
+ v1 = MergingVersion.new(name: 'foo1')
47
+ v2 = MergingVersion.new(name: 'foo1', socks: 'stripey')
48
+ v3 = MergingVersion.new(hat: 'silly')
49
+ merge([v1, v2, v3]).resolution.snapshot.should include(name: 'foo1', socks: 'stripey', hat: 'silly')
50
+ end
51
+
52
+ it 'should save the partial merge result' do
53
+ v1 = MergingVersion.new(name: 'foo1', socks: 'stripey')
54
+ v2 = MergingVersion.new(name: 'foo2', socks: 'argyle')
55
+ v3 = MergingVersion.new(name: 'foo3', socks: 'argyle')
56
+ p1 = MergingVersion.new(name: 'foo', socks: 'argyle')
57
+ v1.parents = v2.parents = v3.parents = p1
58
+ merge([v1, v2, v3]).conflicts.should include(name: ['foo1', 'foo2', 'foo3'])
59
+ merge([v1, v2, v3]).resolution.snapshot.should include(socks: 'stripey')
60
+ end
61
+
62
+ it 'should set parents' do
63
+ v1 = MergingVersion.new(name: 'foo1', socks: 'stripey')
64
+ v2 = MergingVersion.new(name: 'foo2', socks: 'argyle')
65
+ v3 = MergingVersion.new(name: 'foo3', socks: 'argyle')
66
+ merge([v1, v2, v3]).resolution.parents.should == [v1, v2, v3]
67
+ end
68
+
69
+ def merge(versions, options={})
70
+ s = HasVersions::Merge::Octopus.new(versions, options)
71
+ s.merge
72
+ s
73
+ end
74
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe HasVersions::Merge::ThreeWay do
4
+
5
+ it 'should only accept 2 versions' do
6
+ expect { merge([MergingVersion.new]) }.to raise_error(HasVersions::MergeFailed)
7
+ expect { merge([MergingVersion.new]*3) }.to raise_error(HasVersions::MergeFailed)
8
+ end
9
+
10
+ it 'should merge identical versions' do
11
+ v1 = MergingVersion.new(name: 'foo1')
12
+ v2 = MergingVersion.new(name: 'foo1')
13
+ merge([v1, v2]).resolution.snapshot.should include(name: 'foo1')
14
+ end
15
+
16
+ it 'should detect conflicts' do
17
+ v1 = MergingVersion.new(name: 'foo1')
18
+ v2 = MergingVersion.new(name: 'foo2')
19
+ merge([v1, v2]).conflicts.should include(name: ['foo1', 'foo2'])
20
+ end
21
+
22
+ it 'should merge ancestors cleanly' do
23
+ v1 = MergingVersion.new(name: 'foo1')
24
+ v2 = MergingVersion.new(name: 'foo2')
25
+
26
+ v2.parents = [v1]
27
+ merge([v1, v2]).conflicts.should be_empty
28
+ merge([v1, v2]).resolution.snapshot.should include(name: 'foo2')
29
+ end
30
+
31
+ it 'should merge missing attributes' do
32
+ v1 = MergingVersion.new(name: 'foo1')
33
+ v2 = MergingVersion.new(name: 'foo1', socks: 'stripey')
34
+ merge([v1, v2]).resolution.snapshot.should include(name: 'foo1', socks: 'stripey')
35
+ end
36
+
37
+ it 'should save the partial merge result' do
38
+ v1 = MergingVersion.new(name: 'foo1', socks: 'stripey')
39
+ v2 = MergingVersion.new(name: 'foo2', socks: 'argyle')
40
+ p1 = MergingVersion.new(name: 'foo', socks: 'argyle')
41
+ v1.parents = v2.parents = p1
42
+ merge([v1, v2]).conflicts.should include(name: ['foo1', 'foo2'])
43
+ merge([v1, v2]).resolution.snapshot.should include(socks: 'stripey')
44
+ end
45
+
46
+ it 'should set parents' do
47
+ v1 = MergingVersion.new(name: 'foo1', socks: 'stripey')
48
+ v2 = MergingVersion.new(name: 'foo2', socks: 'argyle')
49
+ merge([v1, v2]).resolution.parents.should == [v1, v2]
50
+ end
51
+
52
+ def merge(versions, options={})
53
+ s = HasVersions::Merge::ThreeWay.new(versions, options)
54
+ s.merge
55
+ s
56
+ end
57
+ end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  class ResetObjectVersion < Version; end
4
4
 
@@ -12,7 +12,6 @@ class ResetObject
12
12
 
13
13
  has_versions(ResetObjectVersion) do
14
14
  attribute :name
15
- attribute :set, expected: Set, decoder: lambda { |value| Set.new(value) }
16
15
  end
17
16
  end
18
17
 
@@ -1,18 +1,17 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  class StagingObjectVersion < Version; end
4
4
 
5
- class StagingObject
6
- include HasVersions::Versioned
7
- include HasVersions::Stage
8
-
9
- attr_accessor :name
10
-
11
- has_versions(StagingObjectVersion) do
12
- attribute :name
13
- end
14
- end
15
-
16
- describe StagingObject do
17
- end
18
-
5
+ # class StagingObject
6
+ # include HasVersions::Versioned
7
+ # include HasVersions::Stage
8
+ #
9
+ # attr_accessor :name
10
+ #
11
+ # has_versions(StagingObjectVersion) do
12
+ # attribute :name
13
+ # end
14
+ # end
15
+ #
16
+ # describe StagingObject do
17
+ # end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  class VersionedObject
4
4
  include HasVersions::Versioned
@@ -44,16 +44,16 @@ describe VersionedObject do
44
44
  v
45
45
  end
46
46
 
47
- its(:versioned_attributes) { should have_key(:name).with_value('foo') }
47
+ its(:versioned_attributes) { should include(name: 'foo') }
48
48
 
49
49
  its(:to_version) { should be_a(VersionedObjectVersion) }
50
50
 
51
51
  it 'should set attributes in to_version' do
52
- subject.to_version.snapshot.should have_key(:name).with_value('foo')
52
+ subject.to_version.snapshot.should include(name: 'foo')
53
53
  end
54
54
 
55
55
  it 'should encode attributes' do
56
- subject.to_version.snapshot.should have_key(:time).with_value("02:43AM")
56
+ subject.to_version.snapshot.should include(time: "02:43AM")
57
57
  end
58
58
  end
59
59
  end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe 'VersioningKey' do
4
4
  describe 'UUID' do
data/spec/spec_helper.rb CHANGED
@@ -1,17 +1,16 @@
1
- require 'simplecov'
2
- SimpleCov.start do
3
- add_filter '/spec/'
4
- add_filter '/features/'
5
- end
1
+ # $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ # $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ # p "#{$LOAD_PATH.inspect}"
6
5
 
7
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
- $LOAD_PATH.unshift(File.dirname(__FILE__))
9
6
  require 'rspec'
10
7
  require 'has_versions'
11
8
 
12
9
  # Requires supporting files with custom matchers and macros, etc,
13
10
  # in ./support/ and its subdirectories.
14
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
11
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each do |f|
12
+ require f
13
+ end
15
14
 
16
15
  RSpec.configure do |config|
17
16
  config.mock_with :rspec
@@ -5,4 +5,3 @@ RSpec::Matchers.define :be_a_uuid do |expected|
5
5
  actual =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ # c14ba018-2f15-11e0-9a06-b6e5e5c60f13
6
6
  end
7
7
  end
8
-
@@ -5,3 +5,13 @@ class Version
5
5
  @snapshot = (snapshot || {}).with_indifferent_access
6
6
  end
7
7
  end
8
+
9
+ class MergingVersion < Version
10
+ def parents
11
+ @parents || []
12
+ end
13
+
14
+ def parents=(parents)
15
+ @parents = Array(parents)
16
+ end
17
+ end