eternity 0.0.3 → 0.0.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9bb28d44f6a6270e1aa8d7da6b66067d5a097dbb
4
- data.tar.gz: 1b316ac4f934458b9807717928f8bfd093453a1b
3
+ metadata.gz: 319437ad396ef5f306feb9578edf4a890dc2ecfc
4
+ data.tar.gz: e2ae5f04b986d60ab7a5fdcd26fe3c7614f0c1a7
5
5
  SHA512:
6
- metadata.gz: 4dc81d334ca41182d027060ec32d1761856f80f8ca5de8e1ad79c5aca4de91026ce439a7ee9d68da1ed32d1a833021588a65653af195881d7da5f3f3af531fcc
7
- data.tar.gz: 72308247b234e76c85ad6428860914ed8cce6bc54c073de199c41bb94354d984542baad5dd28a28a3e577ed699ea04a1fc0a6e22f275aa8a491a7e4dcdbafb3d
6
+ metadata.gz: c95bcefe396e0751d1165ae01b69d7500b277c2ab48228c82fda06f5bae0de1c7b651c02852c9b3b9035d8c68521c05f2ba071e47576507861d0ed8c4976656f
7
+ data.tar.gz: b1082bb669fedc219e0a82326ecbb8298e70cc0365393935cf49384c1f92533342c1af44891cdb100687d1b6ffc29da32107fe5784474b2080ca59fdfb6b158a
data/lib/eternity/blob.rb CHANGED
@@ -41,8 +41,15 @@ module Eternity
41
41
  end
42
42
 
43
43
  def normalize(data)
44
- sorted_data = Hash[data.sort_by { |k,v| k.to_s }]
45
- sorted_data.each { |k,v| sorted_data[k] = v.utc.strftime TIME_FORMAT if v.respond_to? :utc }
44
+ case data
45
+ when Hash
46
+ sorted_data = Hash[data.sort_by { |k,v| k.to_s }]
47
+ sorted_data.each { |k,v| sorted_data[k] = v.utc.strftime TIME_FORMAT if v.respond_to? :utc }
48
+ when Array
49
+ data.map { |d| normalize d }
50
+ else
51
+ data
52
+ end
46
53
  end
47
54
 
48
55
  def clear_cache
@@ -46,19 +46,37 @@ module Eternity
46
46
  Commit.new data['base']
47
47
  end
48
48
 
49
- def history_times
50
- data['history_times'] ? Blob.read(:history_times, data['history_times']) : {}
49
+ def history_ids
50
+ if data['history']
51
+ Blob.read :history, data['history']
52
+ else
53
+ # Backward compatibility
54
+ cache_key = Eternity.keyspace[:cache][:history][id]
55
+ return Eternity.redis.call 'LRANGE', cache_key, 0, -1 if Eternity.redis.call('EXISTS', cache_key) == 1
56
+
57
+ commit_ids =
58
+ if parent_ids.count == 2
59
+ current_history_ids = [parent_ids[0]] + Commit.new(parent_ids[0]).history_ids
60
+ target_history_ids = [parent_ids[1]] + Commit.new(parent_ids[1]).history_ids
61
+ current_history_ids - target_history_ids + target_history_ids
62
+ else
63
+ parent_id = parent_ids[0]
64
+ parent_id ? [parent_id] + Commit.new(parent_id).history_ids : []
65
+ end
66
+
67
+ Eternity.redis.call 'RPUSH', cache_key, *commit_ids
68
+
69
+ commit_ids
70
+ end
51
71
  end
52
72
 
53
73
  def history
54
- history_times.sort_by { |id, time| time }
55
- .map { |id, time| Commit.new id }
56
- .reverse
74
+ history_ids.map { |id| Commit.new id }
57
75
  end
58
76
 
59
77
  def fast_forward?(commit)
60
78
  return true if commit.nil?
61
- history_times.key? commit.id
79
+ history_ids.include? commit.id
62
80
  end
63
81
 
64
82
  def first?
@@ -91,21 +109,26 @@ module Eternity
91
109
  raise 'Author must be present' if options[:author].to_s.strip.empty?
92
110
  raise 'Message must be present' if options[:message].to_s.strip.empty?
93
111
 
94
- history_times = options[:parents].compact.each_with_object({}) do |id, hash|
95
- commit = Commit.new id
96
- hash.merge! id => commit.time
97
- hash.merge! commit.history_times
98
- end
112
+ # TODO: Move to Repository and Patch
113
+ history =
114
+ if options[:parents].count == 2
115
+ current_history_ids = [options[:parents][0]] + Commit.new(options[:parents][0]).history_ids
116
+ target_history_ids = [options[:parents][1]] + Commit.new(options[:parents][1]).history_ids
117
+ current_history_ids - target_history_ids + target_history_ids
118
+ else
119
+ parent_id = options[:parents][0]
120
+ parent_id ? [parent_id] + Commit.new(parent_id).history_ids : []
121
+ end
99
122
 
100
123
  data = {
101
- time: options.fetch(:time) { Time.now },
124
+ time: Time.now,
102
125
  author: options.fetch(:author),
103
126
  message: options.fetch(:message),
104
127
  parents: options.fetch(:parents),
105
128
  index: options.fetch(:index),
106
129
  delta: options.fetch(:delta),
107
130
  base: options[:parents].count == 2 ? options.fetch(:base) : options[:parents].first,
108
- history_times: Blob.write(:history_times, history_times)
131
+ history: Blob.write(:history, history)
109
132
  }
110
133
 
111
134
  new Blob.write(:commit, data)
@@ -14,33 +14,41 @@ module Eternity
14
14
  end
15
15
  end
16
16
 
17
- def merge(deltas)
17
+ def merge(deltas, base_index)
18
18
  union(deltas).each_with_object({}) do |(collection, elements), hash|
19
19
  hash[collection] = {}
20
20
  elements.each do |id, changes|
21
- change = TrackFlatter.flatten changes
22
- hash[collection][id] = TrackFlatter.flatten changes if change
21
+ base_data = base_index[collection].include?(id) ? base_index[collection][id].data : {}
22
+ changes.each do |change|
23
+ current_change = change
24
+ if hash[collection][id]
25
+ current_change = TrackFlatter.flatten [hash[collection][id], change]
26
+ if current_change && [INSERT, UPDATE].include?(current_change['action'])
27
+ current_change['data'] = ConflictResolver.resolve hash[collection][id]['data'] || base_data,
28
+ change['data'],
29
+ base_data
30
+ end
31
+ elsif hash[collection].key?(id) && change['action'] == DELETE
32
+ current_change = nil
33
+ end
34
+ hash[collection][id] = current_change
35
+ end
36
+ hash[collection].delete id unless hash[collection][id]
23
37
  end
24
- end
25
- end
26
-
27
- def between(commit_1, commit_2)
28
- commits = ([commit_2] + commit_2.history) - ([commit_1] + commit_1.history)
29
- merge commits.reverse.map(&:delta)
38
+ hash.delete collection if hash[collection].empty?
39
+ end
30
40
  end
31
41
 
32
- def revert(delta, commit)
33
- commit.with_index do |index|
34
- delta.each_with_object({}) do |(collection, changes), hash|
35
- hash[collection] = {}
36
- changes.each do |id, change|
37
- hash[collection][id] =
38
- case change['action']
39
- when INSERT then {'action' => DELETE}
40
- when UPDATE then {'action' => UPDATE, 'data' => index[collection][id].data}
41
- when DELETE then {'action' => INSERT, 'data' => index[collection][id].data}
42
- end
43
- end
42
+ def revert(delta, index)
43
+ delta.each_with_object({}) do |(collection, changes), hash|
44
+ hash[collection] = {}
45
+ changes.each do |id, change|
46
+ hash[collection][id] =
47
+ case change['action']
48
+ when INSERT then {'action' => DELETE}
49
+ when UPDATE then {'action' => UPDATE, 'data' => index[collection][id].data}
50
+ when DELETE then {'action' => INSERT, 'data' => index[collection][id].data}
51
+ end
44
52
  end
45
53
  end
46
54
  end
@@ -26,117 +26,87 @@ module Eternity
26
26
  @delta ||= TransparentProxy.new { calculate_delta }
27
27
  end
28
28
 
29
- end
30
-
29
+ def base_history
30
+ @base_history ||= [base_commit] + base_commit.history
31
+ end
31
32
 
32
- class Merge
33
-
34
- extend Log
35
- include Common
33
+ def current_history
34
+ @current_history ||= [current_commit] + current_commit.history - base_history
35
+ end
36
36
 
37
- def base_delta
38
- @base_delta ||= merged? ? {} : Delta.merge([current_delta, delta])
37
+ def target_history
38
+ @target_history ||= [target_commit] + target_commit.history - base_history
39
39
  end
40
40
 
41
- def merged?
42
- @merged ||= current_commit == target_commit ||
43
- target_commit.fast_forward?(current_commit) ||
44
- current_commit.fast_forward?(target_commit)
41
+ def remaining_history
42
+ @remaining_history ||= current_history - target_history
45
43
  end
46
44
 
47
45
  private
48
46
 
49
- def current_delta
50
- @current_delta ||= Delta.between target_commit, current_commit
51
- end
47
+ def calculate_delta
48
+ base_commit.with_index do |base_index|
49
+ current_commit.with_index do |current_index|
50
+
51
+ current_delta = Delta.merge current_history.reverse.map(&:delta), base_index
52
+ target_delta = Delta.merge target_history.reverse.map(&:delta), base_index
53
+ revert_delta = Delta.revert current_delta, base_index
52
54
 
53
- def target_delta
54
- @target_delta ||= Delta.between current_commit, target_commit
55
- end
55
+ merged_delta = merge_deltas current_delta, target_delta, revert_delta, base_index
56
56
 
57
- def calculate_delta
58
- return {} if merged?
57
+ merged_delta.each_with_object({}) do |(collection, elements), hash|
58
+ hash[collection] = {}
59
59
 
60
- base_commit.with_index do |base_index|
61
- target_delta.each_with_object({}) do |(collection, elements), hash|
62
- hash[collection] = {}
63
-
64
- elements.each do |id, change|
65
- if change['action'] == INSERT && current_action_for(collection, id) == INSERT
66
- data = ConflictResolver.resolve current_delta[collection][id]['data'],
67
- change['data']
68
- change = {'action' => UPDATE, 'data' => data}
69
-
70
- elsif change['action'] == UPDATE
71
- if current_action_for(collection, id) == UPDATE
72
- data = ConflictResolver.resolve current_delta[collection][id]['data'],
73
- change['data'],
74
- base_index[collection][id].data
75
- change = change.merge 'data' => data
76
- elsif current_action_for(collection, id) == DELETE
77
- change = {'action' => INSERT, 'data' => change['data']}
60
+ elements.each do |id, change|
61
+ if change['action'] == UPDATE && current_index[collection][id].sha1 == Blob.digest(Blob.serialize(change['data']))
62
+ change = nil
78
63
  end
79
-
80
- elsif change['action'] == DELETE && current_action_for(collection, id) == DELETE
81
- change = nil
64
+ hash[collection][id] = change if change
82
65
  end
83
66
 
84
- hash[collection][id] = change if change
67
+ hash.delete collection if hash[collection].empty?
85
68
  end
86
-
87
- hash.delete collection if hash[collection].empty?
88
69
  end
89
70
  end
90
71
  end
91
72
 
92
- def has_current_changes_for?(collection, id)
93
- current_delta.key?(collection) && current_delta[collection].key?(id)
94
- end
95
-
96
- def current_action_for(collection, id)
97
- current_delta[collection][id]['action'] if has_current_changes_for? collection, id
98
- end
99
-
100
- log :calculate_delta
101
73
  end
102
74
 
103
75
 
104
- class Diff
105
-
76
+ class Merge
106
77
  extend Log
107
78
  include Common
108
79
 
80
+ def merged?
81
+ @merged ||= current_commit == target_commit ||
82
+ target_commit.fast_forward?(current_commit) ||
83
+ current_commit.fast_forward?(target_commit)
84
+ end
85
+
109
86
  private
110
87
 
111
- def current_delta
112
- @current_delta ||= Delta.between base_commit, current_commit
88
+ def calculate_delta
89
+ return {} if merged?
90
+ super
113
91
  end
114
92
 
115
- def target_delta
116
- @target_delta ||= Delta.between base_commit, target_commit
93
+ def merge_deltas(current_delta, target_delta, revert_delta, base_index)
94
+ remaining_delta = Delta.merge remaining_history.reverse.map(&:delta), base_index
95
+ Delta.merge [revert_delta, target_delta, remaining_delta], base_index
117
96
  end
118
97
 
119
- def diff_delta
120
- @diff_delta ||= Delta.merge [Delta.revert(current_delta, base_commit), target_delta]
121
- end
98
+ log :calculate_delta
99
+ end
122
100
 
123
- def calculate_delta
124
- target_commit.with_index do |target_index|
125
- diff_delta.each_with_object({}) do |(collection, elements), hash|
126
- hash[collection] = {}
127
-
128
- elements.each do |id, change|
129
- if change['data']
130
- sha1 = Blob.digest(Blob.serialize(change['data']))
131
- change['data'] = target_index[collection][id].data if target_index[collection].include?(id) && sha1 != target_index[collection][id].sha1
132
- end
133
101
 
134
- hash[collection][id] = change if change
135
- end
102
+ class Diff
103
+ extend Log
104
+ include Common
136
105
 
137
- hash.delete collection if hash[collection].empty?
138
- end
139
- end
106
+ private
107
+
108
+ def merge_deltas(current_delta, target_delta, revert_delta, base_index)
109
+ Delta.merge [revert_delta, target_delta], base_index
140
110
  end
141
111
 
142
112
  log :calculate_delta
@@ -62,8 +62,7 @@ module Eternity
62
62
 
63
63
  locker.lock! :commit do
64
64
  commit! message: options.fetch(:message),
65
- author: options.fetch(:author),
66
- time: options.fetch(:time) { Time.now }
65
+ author: options.fetch(:author)
67
66
  end
68
67
  end
69
68
 
@@ -134,7 +133,9 @@ module Eternity
134
133
 
135
134
  def revert
136
135
  locker.lock! :revert do
137
- Delta.revert(delta, current_commit).tap { tracker.revert }
136
+ current_commit.with_index do |index|
137
+ Delta.revert(delta, index).tap { tracker.revert }
138
+ end
138
139
  end
139
140
  end
140
141
 
@@ -189,12 +190,11 @@ module Eternity
189
190
 
190
191
  raise 'Already merged' if patch.merged?
191
192
 
192
- commit! message: "Merge #{target_commit.short_id} into #{current_commit.short_id} (#{name})",
193
- author: 'System',
194
- parents: [current_commit.id, target_commit.id],
195
- index: write_index(patch.delta),
196
- base: patch.base_commit.id,
197
- base_delta: Blob.write(:delta, patch.base_delta)
193
+ commit! message: "Merge #{target_commit.short_id} into #{current_commit.short_id} (#{name})",
194
+ author: 'System',
195
+ parents: [current_commit.id, target_commit.id],
196
+ index: write_index(patch.delta),
197
+ base: patch.base_commit.id
198
198
 
199
199
  patch.delta
200
200
  end
@@ -1,3 +1,3 @@
1
1
  module Eternity
2
- VERSION = '0.0.3'
2
+ VERSION = '0.0.4'
3
3
  end
data/spec/delta_spec.rb CHANGED
@@ -6,7 +6,15 @@ describe 'Delta' do
6
6
  let(:repo_2) { Repository.new :test_2 }
7
7
  let(:repo_3) { Repository.new :test_3 }
8
8
 
9
- it 'Merge and Checkout' do
9
+ it 'Merge -> Checkout (2 times)' do
10
+ # P P P
11
+ # REPO 1: (*)--(1)---(4)---(5) (6)--(8) (10)
12
+ # \ \ / \ / \
13
+ # REPO 2: -(2)--(3)---(6)--(7)--(9)--(10) \
14
+ # \ MP M P \
15
+ # REPO 3: ------(11) (10)
16
+ #
17
+
10
18
  repo_1[:countries].insert 'AR', name: 'Argentina'
11
19
  commit_1 = repo_1.commit author: 'User 1', message: 'Commit 1'
12
20
  repo_1.push
@@ -17,10 +25,10 @@ describe 'Delta' do
17
25
  repo_2[:countries].insert 'BR', name: 'Brasil'
18
26
  commit_2 = repo_2.commit author: 'User 2', message: 'Commit 2'
19
27
 
20
- repo_2[:countries].update 'AR', name: 'Argentina', code: 54, capital: '...'
28
+ repo_2[:countries].update 'AR', name: 'Argentina', number: 54, capital: 'CABA'
21
29
  commit_3 = repo_2.commit author: 'User 2', message: 'Commit 3'
22
30
 
23
- repo_1[:countries].update 'AR', name: 'Argentina', capital: 'CABA'
31
+ repo_1[:countries].update 'AR', name: 'Argentina', capital: '...', code: 'ARG'
24
32
  commit_4 = repo_1.commit author: 'User 1', message: 'Commit 4'
25
33
 
26
34
  repo_1[:countries].insert 'CL', name: 'Chile'
@@ -32,12 +40,12 @@ describe 'Delta' do
32
40
  commit_6 = repo_2.current_commit # Merge
33
41
 
34
42
  delta.must_equal 'countries' => {
35
- 'AR' => {'action' => 'update', 'data' => {'name' => 'Argentina', 'code' => 54, 'capital' => 'CABA'}},
43
+ 'AR' => {'action' => 'update', 'data' => {'name' => 'Argentina', 'number' => 54, 'code' => 'ARG', 'capital' => 'CABA'}},
36
44
  'CL' => {'action' => 'insert', 'data' => {'name' => 'Chile'}}
37
45
  }
38
46
 
39
47
  commit_6.must_equal_index 'countries' => {
40
- 'AR' => digest(name: 'Argentina', code: 54, capital: 'CABA'),
48
+ 'AR' => digest(name: 'Argentina', number: 54, code: 'ARG', capital: 'CABA'),
41
49
  'BR' => digest(name: 'Brasil'),
42
50
  'CL' => digest(name: 'Chile')
43
51
  }
@@ -49,9 +57,8 @@ describe 'Delta' do
49
57
  repo_1.current_commit.must_equal commit_6
50
58
 
51
59
  delta.must_equal 'countries' => {
52
- 'AR' => {'action' => 'update', 'data' => {'name' => 'Argentina', 'code' => 54, 'capital' => 'CABA'}},
53
- 'BR' => {'action' => 'insert', 'data' => {'name' => 'Brasil'}},
54
- 'CL' => {'action' => 'update', 'data' => {'name' => 'Chile'}}
60
+ 'AR' => {'action' => 'update', 'data' => {'name' => 'Argentina', 'number' => 54, 'code' => 'ARG', 'capital' => 'CABA'}},
61
+ 'BR' => {'action' => 'insert', 'data' => {'name' => 'Brasil'}}
55
62
  }
56
63
 
57
64
  repo_2[:countries].delete 'CL'
@@ -71,7 +78,7 @@ describe 'Delta' do
71
78
  }
72
79
 
73
80
  commit_9.must_equal_index 'countries' => {
74
- 'AR' => digest(name: 'Argentina', code: 54, capital: 'CABA'),
81
+ 'AR' => digest(name: 'Argentina', number: 54, code: 'ARG', capital: 'CABA'),
75
82
  'BR' => digest(name: 'Brasil'),
76
83
  'PY' => digest(name: 'Paraguay'),
77
84
  'CO' => digest(name: 'Colombia')
@@ -101,13 +108,19 @@ describe 'Delta' do
101
108
 
102
109
  delta.must_equal 'countries' => {
103
110
  'UY' => {'action' => 'delete'},
104
- 'AR' => {'action' => 'update', 'data' => {'name' => 'Argentina', 'code' => 54, 'capital' => 'CABA'}},
111
+ 'AR' => {'action' => 'update', 'data' => {'name' => 'Argentina', 'number' => 54, 'code' => 'ARG', 'capital' => 'CABA'}},
105
112
  'BR' => {'action' => 'insert', 'data' => {'name' => 'Brasil'}},
106
113
  'PY' => {'action' => 'insert', 'data' => {'name' => 'Paraguay'}}
107
114
  }
108
115
  end
109
116
 
110
117
  it 'Commit -> Pull -> Push (multiple times)' do
118
+ # P MP MP M
119
+ # REPO 1: (*)--(1)--(3)--(4)--(7)--(8)--(11)--(12)
120
+ # \ / \ / \ /
121
+ # REPO 2: ---(2)--(5)--(6)--(9)--(10)
122
+ # P MP MP
123
+
111
124
  repo_1[:countries].insert 'AR', name: 'Argentina'
112
125
  repo_1.commit author: 'User 1', message: 'Added Argentina'
113
126
  repo_1.push
@@ -216,4 +229,88 @@ describe 'Delta' do
216
229
  }
217
230
  end
218
231
 
232
+ it 'Merge -> Merge -> Checkout' do
233
+ # P MP P
234
+ # REPO 1: (*)--(1)--(2)------(6)--(7)
235
+ # \ MP / \
236
+ # REPO 2: --(3)--(5) (7)
237
+ # \ /
238
+ # REPO 3: -(4)-
239
+ # P
240
+
241
+ repo_1[:countries].insert 'AR', name: 'Argentina'
242
+ repo_1.commit author: 'User 1', message: 'Commit 1'
243
+ repo_1.push
244
+
245
+ delta = repo_2.pull
246
+ delta.must_equal 'countries' => {
247
+ 'AR' => {'action' => 'insert', 'data' => {'name' => 'Argentina'}}
248
+ }
249
+
250
+ repo_2.current_commit.must_equal_index 'countries' => {
251
+ 'AR' => digest(name: 'Argentina'),
252
+ }
253
+
254
+ delta = repo_3.pull
255
+ delta.must_equal 'countries' => {
256
+ 'AR' => {'action' => 'insert', 'data' => {'name' => 'Argentina'}}
257
+ }
258
+
259
+ repo_3.current_commit.must_equal_index 'countries' => {
260
+ 'AR' => digest(name: 'Argentina'),
261
+ }
262
+
263
+ repo_1[:countries].insert 'BR', name: 'Brasil'
264
+ repo_1.commit author: 'User 1', message: 'Commit 2'
265
+
266
+ repo_2[:countries].update 'AR', name: 'Argentina', number: 54
267
+ repo_2.commit author: 'User 2', message: 'Commit 3'
268
+
269
+ repo_3[:countries].insert 'UY', name: 'Uruguay'
270
+ repo_3.commit author: 'User 3', message: 'Commit 4'
271
+ repo_3.push
272
+
273
+ delta = repo_2.pull
274
+ delta.must_equal 'countries' => {
275
+ 'UY' => {'action' => 'insert', 'data' => {'name' => 'Uruguay'}}
276
+ }
277
+
278
+ repo_2.current_commit.must_equal_index 'countries' => {
279
+ 'AR' => digest(name: 'Argentina', number: 54),
280
+ 'UY' => digest(name: 'Uruguay')
281
+ }
282
+
283
+ repo_2.push
284
+
285
+ delta = repo_1.pull
286
+ delta.must_equal 'countries' => {
287
+ 'AR' => {'action' => 'update', 'data' => {'name' => 'Argentina', 'number' => 54}},
288
+ 'UY' => {'action' => 'insert', 'data' => {'name' => 'Uruguay'}}
289
+ }
290
+
291
+ repo_1.current_commit.must_equal_index 'countries' => {
292
+ 'AR' => digest(name: 'Argentina', number: 54),
293
+ 'BR' => digest(name: 'Brasil'),
294
+ 'UY' => digest(name: 'Uruguay')
295
+ }
296
+
297
+ repo_1.push
298
+
299
+ repo_1[:countries].delete 'UY'
300
+ repo_1.commit author: 'User 1', message: 'Commit 7'
301
+ repo_1.push
302
+
303
+ delta = repo_2.pull
304
+ delta.must_equal 'countries' => {
305
+ 'BR' => {'action' => 'insert', 'data' => {'name' => 'Brasil'}},
306
+ 'UY' => {'action' => 'delete'}
307
+ }
308
+
309
+ repo_2.current_commit.must_equal_index 'countries' => {
310
+ 'AR' => digest(name: 'Argentina', number: 54),
311
+ 'BR' => digest(name: 'Brasil')
312
+ }
313
+
314
+ end
315
+
219
316
  end
@@ -0,0 +1,102 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Commit, 'history' do
4
+
5
+ let(:repo_1) { Repository.new :test_1 }
6
+ let(:repo_2) { Repository.new :test_2 }
7
+ let(:repo_3) { Repository.new :test_3 }
8
+
9
+ let(:commits) { Hash.new }
10
+
11
+ def commit(repo, id)
12
+ data = {id: SecureRandom.uuid}
13
+ repo[:countries].insert data[:id], data
14
+ commits[id] = repo.commit author: repo.name, message: "Commit #{id}"
15
+ end
16
+
17
+ def push(repo)
18
+ repo.push
19
+ end
20
+
21
+ def pull(repo, merge_id=nil)
22
+ repo.pull
23
+ commits[merge_id] = repo.current_commit if merge_id
24
+ end
25
+
26
+ def assert_history(expected)
27
+ commits.each do |i,c|
28
+ c.history.must_equal expected[i].map { |e| commits[e] }, "Fail history of ##{i}: #{c}\nExpected: #{expected[i].map{ |c| commits[c].message }} \nActual: #{c.history.map(&:message)}"
29
+ end
30
+ end
31
+
32
+ it 'Test 1' do
33
+ # P M P
34
+ # REPO 1: (*)--(1)--(2)--(4)---(6)---(7)
35
+ # \ / \
36
+ # REPO 2: -----(3)---(5)---(8)
37
+ # P M
38
+
39
+ commit repo_1, 1
40
+ commit repo_1, 2
41
+ push repo_1
42
+ pull repo_2
43
+ commit repo_2, 3
44
+ push repo_2
45
+ commit repo_1, 4
46
+ commit repo_2, 5
47
+ pull repo_1, 6
48
+ commit repo_1, 7
49
+ push repo_1
50
+ pull repo_2, 8
51
+
52
+ assert_history 1 => [],
53
+ 2 => [1],
54
+ 3 => [2,1],
55
+ 4 => [2,1],
56
+ 5 => [3,2,1],
57
+ 6 => [4,3,2,1],
58
+ 7 => [6,4,3,2,1],
59
+ 8 => [5,7,6,4,3,2,1]
60
+ end
61
+
62
+ it 'Test 2' do
63
+ # P M
64
+ # REPO 1: (*)--(1)--(2)------------------(11)
65
+ # \ M P /
66
+ # REPO 2: --(3)--(4)--(7)--(8) /
67
+ # \ / \ /
68
+ # REPO 3: -(5)----(6)--(9)--(10)
69
+ # P MP
70
+
71
+ commit repo_1, 1
72
+ push repo_1
73
+ commit repo_1, 2
74
+ pull repo_2
75
+ commit repo_2, 3
76
+ commit repo_2, 4
77
+ pull repo_3
78
+ commit repo_3, 5
79
+ commit repo_3, 6
80
+ push repo_3
81
+ pull repo_2, 7
82
+ commit repo_2, 8
83
+ push repo_2
84
+ commit repo_3, 9
85
+ pull repo_3, 10
86
+ push repo_3
87
+ pull repo_1, 11
88
+
89
+ assert_history 1 => [],
90
+ 2 => [1],
91
+ 3 => [1],
92
+ 4 => [3,1],
93
+ 5 => [1],
94
+ 6 => [5,1],
95
+ 7 => [4,3,6,5,1],
96
+ 8 => [7,4,3,6,5,1],
97
+ 9 => [6,5,1],
98
+ 10 => [9,8,7,4,3,6,5,1],
99
+ 11 => [2,10,9,8,7,4,3,6,5,1]
100
+ end
101
+
102
+ end
data/spec/log_spec.rb CHANGED
@@ -29,21 +29,20 @@ describe Repository, 'Log' do
29
29
  end
30
30
 
31
31
  it 'Merge' do
32
- time = Time.now
33
32
  repository[:countries].insert 'AR', name: 'Argentina'
34
- commit_1 = repository.commit author: 'User', message: 'Commit 1', time: time
33
+ commit_1 = repository.commit author: 'User', message: 'Commit 1'
35
34
  repository.push
36
35
 
37
36
  other_repository = Repository.new :other
38
37
  other_repository.pull
39
38
 
40
39
  other_repository[:countries].insert 'UY', name: 'Uruguay'
41
- commit_2 = other_repository.commit author: 'User', message: 'Commit 2', time: time + 1
40
+ commit_2 = other_repository.commit author: 'User', message: 'Commit 2'
42
41
 
43
42
  other_repository.push
44
43
 
45
44
  repository[:countries].insert 'BR', name: 'Brasil'
46
- commit_3 = repository.commit author: 'User', message: 'Commit 3', time: time + 2
45
+ commit_3 = repository.commit author: 'User', message: 'Commit 3'
47
46
 
48
47
  repository.pull
49
48
 
@@ -0,0 +1,134 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Repository, 'Merge' do
4
+
5
+ let(:repo_1) { Repository.new :test_1 }
6
+ let(:repo_2) { Repository.new :test_2 }
7
+ let(:repo_3) { Repository.new :test_3 }
8
+
9
+ let(:commits) { Hash.new }
10
+
11
+ def commit(id, repo, &block)
12
+ repo[:countries].instance_eval &block
13
+ commits[id] = repo.commit author: repo.name, message: "Commit #{id}"
14
+ end
15
+
16
+ def merge(id, repo, target_id)
17
+ delta = repo.merge commit: commits[target_id].id
18
+ commits[id] = repo.current_commit
19
+ yield delta['countries'] if block_given?
20
+ delta
21
+ end
22
+
23
+ def assert_history(id, history_ids)
24
+ commits[id].history.must_equal history_ids.map { |c| commits[c] }
25
+ end
26
+
27
+ def assert_index(id, expected)
28
+ commits[id].must_equal_index 'countries' => expected
29
+ end
30
+
31
+ it 'Delta, index and history' do
32
+ # REPO 1: (*)---(1)--(2)--(5)---(6)---(9)--(11)
33
+ # \ / \ /
34
+ # REPO 2: -(3)---(4)--(7)--(8)--(10)
35
+
36
+ commit 1, repo_1 do
37
+ insert 'AR', name: 'Argentina'
38
+ insert 'BR', name: 'Brasil'
39
+ end
40
+
41
+ assert_history 1, []
42
+ assert_index 1, 'AR' => digest(name: 'Argentina'),
43
+ 'BR' => digest(name: 'Brasil')
44
+
45
+ commit 2, repo_1 do
46
+ delete 'BR'
47
+ end
48
+
49
+ assert_history 2, [1]
50
+ assert_index 2, 'AR' => digest(name: 'Argentina')
51
+
52
+ repo_2.checkout commit: commits[1].id
53
+
54
+ commit 3, repo_2 do
55
+ insert 'UY', name: 'Uruguay'
56
+ end
57
+
58
+ assert_history 3, [1]
59
+ assert_index 3, 'AR' => digest(name: 'Argentina'),
60
+ 'BR' => digest(name: 'Brasil'),
61
+ 'UY' => digest(name: 'Uruguay')
62
+
63
+ commit 4, repo_2 do
64
+ update 'BR', name: 'Brasil', number: 55
65
+ end
66
+
67
+ assert_history 4, [3,1]
68
+ assert_index 4, 'AR' => digest(name: 'Argentina'),
69
+ 'BR' => digest(name: 'Brasil', number: 55),
70
+ 'UY' => digest(name: 'Uruguay')
71
+
72
+ commit 5, repo_1 do
73
+ update 'AR', name: 'Argentina', number: 54
74
+ end
75
+
76
+ assert_history 5, [2, 1]
77
+ assert_index 5, 'AR' => digest(name: 'Argentina', number: 54)
78
+
79
+ merge 6, repo_1, 4 do |delta|
80
+ delta.must_equal 'UY' => {'action' => 'insert', 'data' => {'name' => 'Uruguay'}}
81
+ end
82
+
83
+ assert_history 6, [5,2,4,3,1]
84
+ assert_index 6, 'AR' => digest(name: 'Argentina', number: 54),
85
+ 'UY' => digest(name: 'Uruguay')
86
+
87
+ commit 7, repo_2 do
88
+ update 'AR', name: 'Argentina', code: 'ARG'
89
+ end
90
+
91
+ assert_history 7, [4,3,1]
92
+ assert_index 7, 'AR' => digest(name: 'Argentina', code: 'ARG'),
93
+ 'BR' => digest(name: 'Brasil', number: 55),
94
+ 'UY' => digest(name: 'Uruguay')
95
+
96
+ merge 8, repo_2, 6 do |delta|
97
+ delta.must_equal 'BR' => {'action' => 'delete'},
98
+ 'AR' => {'action' => 'update', 'data' => {'name' => 'Argentina', 'code' => 'ARG', 'number' => 54}}
99
+ end
100
+
101
+ assert_history 8, [7,6,5,2,4,3,1]
102
+ assert_index 8, 'AR' => digest(name: 'Argentina', code: 'ARG', number: 54),
103
+ 'UY' => digest(name: 'Uruguay')
104
+
105
+ commit 9, repo_1 do
106
+ insert 'CL', name: 'Chile', code: 'CHI'
107
+ end
108
+
109
+ assert_history 9, [6,5,2,4,3,1]
110
+ assert_index 9, 'AR' => digest(name: 'Argentina', number: 54),
111
+ 'UY' => digest(name: 'Uruguay'),
112
+ 'CL' => digest(name: 'Chile', code: 'CHI')
113
+
114
+ commit 10, repo_2 do
115
+ insert 'CL', name: 'Republica de Chile', number: 56
116
+ end
117
+
118
+ assert_history 10, [8,7,6,5,2,4,3,1]
119
+ assert_index 10, 'AR' => digest(name: 'Argentina', code: 'ARG', number: 54),
120
+ 'UY' => digest(name: 'Uruguay'),
121
+ 'CL' => digest(name: 'Republica de Chile', number: 56)
122
+
123
+ merge 11, repo_1, 10 do |delta|
124
+ delta.must_equal 'AR' => {'action' => 'update', 'data' => {'name' => 'Argentina', 'number' => 54, 'code' => 'ARG'}},
125
+ 'CL' => {'action' => 'update', 'data' => {'name' => 'Chile', 'number' => 56, 'code' => 'CHI'}}
126
+ end
127
+
128
+ assert_history 11, [9,10,8,7,6,5,2,4,3,1]
129
+ assert_index 11, 'AR' => digest(name: 'Argentina', code: 'ARG', number: 54),
130
+ 'UY' => digest(name: 'Uruguay'),
131
+ 'CL' => digest(name: 'Chile', number: 56, code: 'CHI')
132
+ end
133
+
134
+ end
@@ -20,14 +20,6 @@ Eternity.configure do |config|
20
20
  config.logger.level = Logger::ERROR
21
21
  end
22
22
 
23
- class Time
24
- @last = Time.now
25
-
26
- def self.now
27
- @last += 1
28
- end
29
- end
30
-
31
23
  class Minitest::Spec
32
24
  def redis
33
25
  Eternity.redis
@@ -37,32 +29,6 @@ class Minitest::Spec
37
29
  Blob.digest(Blob.serialize(data))
38
30
  end
39
31
 
40
- def print_keys
41
- puts Eternity.redis_keys.sort
42
- end
43
-
44
- def print_commit(commit)
45
- puts "COMMIT: #{commit.author} - #{commit.message}"
46
- puts "MERGE: #{commit.merge?}"
47
- puts 'DELTA:'
48
- puts JSON.pretty_generate(commit.delta)
49
- if commit.merge?
50
- puts "BASE: #{commit.base.author} - #{commit.base.message}"
51
- puts 'BASE DELTA:'
52
- puts JSON.pretty_generate(commit.base_delta)
53
- end
54
- puts 'INDEX:'
55
- commit.with_index do |index|
56
- index.each do |collection, collection_index|
57
- puts collection
58
- collection_index.ids.each do |id|
59
- puts "#{id} -> #{collection_index[id].data}"
60
- end
61
- end
62
- end
63
- puts '------------------------------------'
64
- end
65
-
66
32
  after do
67
33
  Eternity.clear_redis
68
34
  Eternity.clear_file_system
data/spec/patch_spec.rb CHANGED
@@ -8,9 +8,9 @@ describe Patch do
8
8
  let(:commits) { Hash.new }
9
9
 
10
10
  before do
11
- # Repo 1: X---(1)---(2)---(3)---(4)---(5)---(8)---(9)---(12)---(13)---(14)
12
- # Repo 2: \ \--(6)---(7)--/ /
13
- # Repo 3: \----------(10)-----------------(11)------/
11
+ # REPO 1: (*)---(1)---(2)---(3)---(4)---(5)---(8)---(9)---(12)---(13)---(14)
12
+ # REPO 2: \ \--(6)---(7)--/ /
13
+ # REPO 3: \----------(10)-----------------(11)------/
14
14
 
15
15
  repo_1[:countries].insert 'AR', name: 'Argentina'
16
16
  commit repo_1, 1
@@ -74,7 +74,6 @@ describe Patch do
74
74
 
75
75
  patch.delta.must_be_empty
76
76
  patch.base_commit.must_equal commits[4]
77
- patch.base_delta.must_be_empty
78
77
  end
79
78
 
80
79
  it 'Diff' do
@@ -95,10 +94,6 @@ describe Patch do
95
94
  'CO' => {'action' => 'insert', 'data' => {'name' => 'Colombia', 'number' => 57}}
96
95
  }
97
96
  patch.base_commit.must_be_nil
98
- patch.base_delta.must_equal 'countries' => {
99
- 'AR' => {'action' => 'insert', 'data' => {'name' => 'Argentina'}},
100
- 'CO' => {'action' => 'insert', 'data' => {'name' => 'Colombia', 'number' => 57}}
101
- }
102
97
  end
103
98
 
104
99
  it 'Diff' do
@@ -120,7 +115,6 @@ describe Patch do
120
115
 
121
116
  patch.delta.must_be_empty
122
117
  patch.base_commit.must_equal commits[1]
123
- patch.base_delta.must_be_empty
124
118
  end
125
119
 
126
120
  it 'Diff' do
@@ -142,7 +136,6 @@ describe Patch do
142
136
 
143
137
  patch.delta.must_be_empty
144
138
  patch.base_commit.must_equal commits[2]
145
- patch.base_delta.must_be_empty
146
139
  end
147
140
 
148
141
  it 'Diff' do
@@ -167,11 +160,6 @@ describe Patch do
167
160
  'CL' => {'action' => 'insert', 'data' => {'name' => 'Chile'}}
168
161
  }
169
162
  patch.base_commit.must_equal commits[3]
170
- patch.base_delta.must_equal 'countries' => {
171
- 'UY' => {'action' => 'update', 'data' => {'name' => 'Republica Oriental del Uruguay'}},
172
- 'AR' => {'action' => 'update', 'data' => {'name' => 'Republica Argentina', 'number' => 54}},
173
- 'CL' => {'action' => 'insert', 'data' => {'name' => 'Chile'}},
174
- }
175
163
  end
176
164
 
177
165
  it 'Diff' do
@@ -194,7 +182,6 @@ describe Patch do
194
182
 
195
183
  patch.delta.must_be_empty
196
184
  patch.base_commit.must_equal commits[3]
197
- patch.base_delta.must_be_empty
198
185
  end
199
186
 
200
187
  it 'Diff' do
data/spec/pull_spec.rb CHANGED
@@ -132,7 +132,7 @@ describe Repository, 'Pull' do
132
132
 
133
133
  delta.must_equal 'countries' => {'AR' => {'action' => 'insert', 'data' => {'name' => 'Argentina 1'}}}
134
134
 
135
- other_repository[:countries].update 'AR', name: 'Argentina 2'
135
+ other_repository[:countries].update 'AR', name: 'Argentina 2', number: 54
136
136
  commit_2 = other_repository.commit author: 'User', message: 'Commit 2'
137
137
  other_repository.push
138
138
 
@@ -140,7 +140,7 @@ describe Repository, 'Pull' do
140
140
  commit_3 = repository.commit author: 'User', message: 'Commit 3'
141
141
  delta = repository.pull
142
142
 
143
- delta.must_equal 'countries' => {'AR' => {'action' => 'update', 'data' => {'name' => 'Argentina 2'}}}
143
+ delta.must_equal 'countries' => {'AR' => {'action' => 'update', 'data' => {'name' => 'Argentina 3', 'number' => 54}}}
144
144
 
145
145
  repository.branches[repository.current_branch].must_equal repository.current_commit.id
146
146
 
@@ -149,7 +149,7 @@ describe Repository, 'Pull' do
149
149
  commit.parents.must_equal [commit_3, commit_2]
150
150
  commit.base.id.must_equal commit_1.id
151
151
  commit.delta.must_be_empty
152
- commit.must_equal_index 'countries' => {'AR' => digest(name: 'Argentina 2')}
152
+ commit.must_equal_index 'countries' => {'AR' => digest(name: 'Argentina 3', number: 54)}
153
153
  end
154
154
  end
155
155
 
@@ -202,7 +202,7 @@ describe Repository, 'Pull' do
202
202
  commit_3 = repository.commit author: 'User', message: 'Commit 3'
203
203
  delta = repository.pull
204
204
 
205
- delta.must_equal 'countries' => {'X' => {'action' => 'update', 'data' => {'name' => 'X1', 'number' => 2, 'code' => 1}}}
205
+ delta.must_equal 'countries' => {'X' => {'action' => 'update', 'data' => {'name' => 'X2', 'number' => 2, 'code' => 1}}}
206
206
 
207
207
  repository.branches[repository.current_branch].must_equal repository.current_commit.id
208
208
 
@@ -213,7 +213,7 @@ describe Repository, 'Pull' do
213
213
  commit.delta.must_be_empty
214
214
  commit.must_equal_index 'countries' => {
215
215
  'AR' => digest(name: 'Argentina'),
216
- 'X' => digest(name: 'X1', 'number' => 2, 'code' => 1)
216
+ 'X' => digest(name: 'X2', 'number' => 2, 'code' => 1)
217
217
  }
218
218
  end
219
219
  end
@@ -234,6 +234,7 @@ describe Repository, 'Pull' do
234
234
 
235
235
  repository[:countries].delete 'AR'
236
236
  commit_3 = repository.commit author: 'User', message: 'Commit 3'
237
+
237
238
  delta = repository.pull
238
239
 
239
240
  delta.must_be_empty
@@ -259,15 +260,16 @@ describe Repository, 'Pull' do
259
260
 
260
261
  delta.must_equal 'countries' => {'AR' => {'action' => 'insert', 'data' => {'name' => 'Argentina'}}}
261
262
 
262
- other_repository[:countries].update 'AR', name: 'Argentina', code: 'ARG'
263
+ other_repository[:countries].delete 'AR'
263
264
  commit_2 = other_repository.commit author: 'User', message: 'Commit 2'
264
265
  other_repository.push
265
266
 
266
- repository[:countries].delete 'AR'
267
+ repository[:countries].update 'AR', name: 'Argentina', code: 'ARG'
267
268
  commit_3 = repository.commit author: 'User', message: 'Commit 3'
269
+
268
270
  delta = repository.pull
269
271
 
270
- delta.must_equal 'countries' => {'AR' => {'action' => 'insert', 'data' => {'name' => 'Argentina', 'code' => 'ARG'}}}
272
+ delta.must_be_empty
271
273
 
272
274
  repository.branches[repository.current_branch].must_equal repository.current_commit.id
273
275
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eternity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel Naiman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-18 00:00:00.000000000 Z
11
+ date: 2015-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redic
@@ -217,9 +217,11 @@ files:
217
217
  - spec/commit_spec.rb
218
218
  - spec/coverage_helper.rb
219
219
  - spec/delta_spec.rb
220
+ - spec/history_spec.rb
220
221
  - spec/index_spec.rb
221
222
  - spec/locking_spec.rb
222
223
  - spec/log_spec.rb
224
+ - spec/merge_spec.rb
223
225
  - spec/minitest_helper.rb
224
226
  - spec/patch_spec.rb
225
227
  - spec/pull_spec.rb
@@ -258,9 +260,11 @@ test_files:
258
260
  - spec/commit_spec.rb
259
261
  - spec/coverage_helper.rb
260
262
  - spec/delta_spec.rb
263
+ - spec/history_spec.rb
261
264
  - spec/index_spec.rb
262
265
  - spec/locking_spec.rb
263
266
  - spec/log_spec.rb
267
+ - spec/merge_spec.rb
264
268
  - spec/minitest_helper.rb
265
269
  - spec/patch_spec.rb
266
270
  - spec/pull_spec.rb