eternity 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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