gitmodel 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +54 -0
- data/LICENSE +20 -0
- data/README.md +143 -0
- data/Rakefile +3 -0
- data/autotest/discover.rb +1 -0
- data/gitmodel.gemspec +36 -0
- data/lib/gitmodel/errors.rb +25 -0
- data/lib/gitmodel/persistable.rb +269 -0
- data/lib/gitmodel/transaction.rb +62 -0
- data/lib/gitmodel.rb +76 -0
- data/spec/gitmodel/persistable_spec.rb +436 -0
- data/spec/gitmodel/transaction_spec.rb +59 -0
- data/spec/gitmodel_spec.rb +39 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/setup.rb +10 -0
- metadata +206 -0
data/lib/gitmodel.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'active_model'
|
5
|
+
require 'active_support/all' # TODO we don't really want all here, clean this up
|
6
|
+
require 'grit'
|
7
|
+
require 'json'
|
8
|
+
require 'lockfile'
|
9
|
+
require 'pp'
|
10
|
+
|
11
|
+
$:.unshift(File.dirname(__FILE__))
|
12
|
+
require 'gitmodel/errors'
|
13
|
+
require 'gitmodel/persistable'
|
14
|
+
require 'gitmodel/transaction'
|
15
|
+
|
16
|
+
module GitModel
|
17
|
+
|
18
|
+
# db_root must be an existing git repo. (It can be created with create_db!)
|
19
|
+
# Bare repositories aren't supported yet, it must be a normal git repo with a
|
20
|
+
# working directory and a '.git' subdirectory.
|
21
|
+
mattr_accessor :db_root
|
22
|
+
self.db_root = './gitmodel-data'
|
23
|
+
|
24
|
+
mattr_accessor :default_branch
|
25
|
+
self.default_branch = 'master'
|
26
|
+
|
27
|
+
mattr_accessor :logger
|
28
|
+
self.logger = ::Logger.new(STDERR)
|
29
|
+
self.logger.level = ::Logger::WARN
|
30
|
+
|
31
|
+
mattr_accessor :git_user_name
|
32
|
+
mattr_accessor :git_user_email
|
33
|
+
|
34
|
+
def self.repo
|
35
|
+
@@repo = Grit::Repo.new(GitModel.db_root)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Create the database defined in db_root. Raises an exception if it exists.
|
39
|
+
def self.create_db!
|
40
|
+
raise "Database #{db_root} already exists!" if File.exist? db_root
|
41
|
+
if db_root =~ /.+\.git/
|
42
|
+
#logger.info "Creating database (bare): #{db_root}"
|
43
|
+
#Grit::Repo.init_bare db_root
|
44
|
+
logger.error "Bare repositories aren't supported yet"
|
45
|
+
else
|
46
|
+
logger.info "Creating database: #{db_root}"
|
47
|
+
Grit::Repo.init db_root
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Delete and re-create the database defined in db_root. Dangerous!
|
52
|
+
def self.recreate_db!
|
53
|
+
logger.info "Deleting database #{db_root}!!"
|
54
|
+
FileUtils.rm_rf db_root
|
55
|
+
create_db!
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.last_commit(branch = nil)
|
59
|
+
branch ||= default_branch
|
60
|
+
# PERFORMANCE Cache this somewhere and update it on commit?
|
61
|
+
# (Need separate instance per branch)
|
62
|
+
|
63
|
+
return nil unless repo.commits(branch).any?
|
64
|
+
|
65
|
+
# We should be able to use just repo.commits(branch).first here but
|
66
|
+
# this is a workaround for this bug:
|
67
|
+
# http://github.com/mojombo/grit/issues/issue/38
|
68
|
+
GitModel.repo.commits("#{branch}^..#{branch}").first || GitModel.repo.commits(branch).first
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.current_tree(branch = nil)
|
72
|
+
c = last_commit(branch)
|
73
|
+
c ? c.tree : nil
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,436 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestEntity
|
4
|
+
include GitModel::Persistable
|
5
|
+
end
|
6
|
+
|
7
|
+
class LintTest < ActiveModel::TestCase
|
8
|
+
include ActiveModel::Lint::Tests
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@model = TestEntity.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe GitModel::Persistable do
|
16
|
+
|
17
|
+
it 'passes ActiveModel lint tests' do
|
18
|
+
|
19
|
+
o = LintTest.new("ActiveModel lint test")
|
20
|
+
o.setup
|
21
|
+
|
22
|
+
# TODO get this list of methods dynamically
|
23
|
+
o.test_to_key
|
24
|
+
o.test_to_param
|
25
|
+
o.test_valid?
|
26
|
+
o.test_persisted?
|
27
|
+
o.test_model_naming
|
28
|
+
o.test_errors_aref
|
29
|
+
o.test_errors_full_messages
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#save' do
|
33
|
+
|
34
|
+
it 'raises an exception if the id is not set' do
|
35
|
+
o = TestEntity.new
|
36
|
+
lambda {o.save}.should raise_error(GitModel::NullId)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'stores an instance in a Git repository in a subdir of db_root named with the id' do
|
40
|
+
id = 'foo'
|
41
|
+
TestEntity.create!(:id => id)
|
42
|
+
|
43
|
+
repo = Grit::Repo.new(GitModel.db_root)
|
44
|
+
(repo.commits.first.tree / File.join(TestEntity.db_subdir, id, 'attributes.json')).data.should_not be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'stores attributes in a JSON file' do
|
48
|
+
id = 'foo'
|
49
|
+
attrs = {:one => 1, :two => 2}
|
50
|
+
TestEntity.create!(:id => id, :attributes => attrs)
|
51
|
+
|
52
|
+
repo = Grit::Repo.new(GitModel.db_root)
|
53
|
+
attrs = (repo.commits.first.tree / File.join(TestEntity.db_subdir, id, 'attributes.json')).data
|
54
|
+
r = JSON.parse(attrs)
|
55
|
+
r.size.should == 2
|
56
|
+
r['one'].should == 1
|
57
|
+
r['two'].should == 2
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'stores blobs in files' do
|
61
|
+
id = 'foo'
|
62
|
+
blobs = {'blob1.txt' => 'This is blob 1'}
|
63
|
+
TestEntity.create!(:id => id, :blobs => blobs)
|
64
|
+
|
65
|
+
repo = Grit::Repo.new(GitModel.db_root)
|
66
|
+
(repo.commits.first.tree / File.join(TestEntity.db_subdir, id, 'blob1.txt')).data.should == 'This is blob 1'
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'can store attributes and blobs' do
|
70
|
+
id = 'foo'
|
71
|
+
attrs = {:one => 1, :two => 2}
|
72
|
+
blobs = {'blob1.txt' => 'This is blob 1'}
|
73
|
+
TestEntity.create!(:id => id, :attributes => attrs, :blobs => blobs)
|
74
|
+
|
75
|
+
r = TestEntity.find('foo')
|
76
|
+
r.attributes['one'].should == 1
|
77
|
+
r.attributes['two'].should == 2
|
78
|
+
r.blobs['blob1.txt'].should == 'This is blob 1'
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns false if the validations failed'
|
82
|
+
|
83
|
+
it 'returns the SHA of the commit if the save was successful'
|
84
|
+
|
85
|
+
it 'deletes blobs that have been removed'
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#save!' do
|
89
|
+
|
90
|
+
it "calls save and returns the non-false and non-nil result"
|
91
|
+
|
92
|
+
it "calls save and raises an exception if the result is nil"
|
93
|
+
|
94
|
+
it "calls save and raises an exception if the result is false"
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#new' do
|
99
|
+
it 'creates a new unsaved instance' do
|
100
|
+
TestEntity.new.new_record?.should be_true
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'takes an optional hash to set id, attributes and blobs' do
|
104
|
+
o = TestEntity.new(:id => 'foo', :attributes => {:one => 1}, :blobs => {'blob1.txt' => 'This is blob 1'})
|
105
|
+
o.id.should == 'foo'
|
106
|
+
o.attributes['one'].should == 1
|
107
|
+
o.blobs['blob1.txt'].should == 'This is blob 1'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe '.create' do
|
112
|
+
|
113
|
+
it 'creates a new instance with the given parameters and calls #save on it' do
|
114
|
+
id = 'foo'
|
115
|
+
attrs = {:one => 1, :two => 2}
|
116
|
+
blobs = {'blob1.txt' => 'This is blob 1'}
|
117
|
+
|
118
|
+
new_mock = mock("new_mock")
|
119
|
+
TestEntity.should_receive(:new).with(:id => id, :attributes => attrs, :blobs => blobs).and_return(new_mock)
|
120
|
+
new_mock.should_receive(:save)
|
121
|
+
|
122
|
+
TestEntity.create(:id => id, :attributes => attrs, :blobs => blobs)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'returns an instance of the record created' do
|
126
|
+
o = TestEntity.create(:id => 'lemur')
|
127
|
+
o.should be_a(TestEntity)
|
128
|
+
o.id.should == 'lemur'
|
129
|
+
end
|
130
|
+
|
131
|
+
describe 'with a single array as a parameter' do
|
132
|
+
|
133
|
+
it 'creates a new instance with each element of the array as parameters and calls #save on it' do
|
134
|
+
args = [
|
135
|
+
{:id => 'foo', :attributes => {:one => 1}, :blobs => {'blob1.txt' => 'This is blob 1'}},
|
136
|
+
{:id => 'bar', :attributes => {:two => 2}, :blobs => {'blob2.txt' => 'This is blob 2'}}
|
137
|
+
]
|
138
|
+
|
139
|
+
new_mock1 = mock("new_mock1")
|
140
|
+
new_mock2 = mock("new_mock2")
|
141
|
+
TestEntity.should_receive(:new).with(args[0]).once.and_return(new_mock1)
|
142
|
+
TestEntity.should_receive(:new).with(args[1]).once.and_return(new_mock2)
|
143
|
+
new_mock1.should_receive(:save)
|
144
|
+
new_mock2.should_receive(:save)
|
145
|
+
|
146
|
+
TestEntity.create(args)
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
describe '.create!' do
|
154
|
+
|
155
|
+
it 'creates a new instance with the given parameters and calls #save! on it' do
|
156
|
+
id = 'foo'
|
157
|
+
attrs = {:one => 1, :two => 2}
|
158
|
+
blobs = {'blob1.txt' => 'This is blob 1'}
|
159
|
+
|
160
|
+
new_mock = mock("new_mock")
|
161
|
+
TestEntity.should_receive(:new).with(:id => id, :attributes => attrs, :blobs => blobs).and_return(new_mock)
|
162
|
+
new_mock.should_receive(:save!)
|
163
|
+
|
164
|
+
TestEntity.create!(:id => id, :attributes => attrs, :blobs => blobs)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'returns an instance of the record created' do
|
168
|
+
o = TestEntity.create!(:id => 'lemur')
|
169
|
+
o.should be_a(TestEntity)
|
170
|
+
o.id.should == 'lemur'
|
171
|
+
end
|
172
|
+
|
173
|
+
describe 'with a single array as a parameter' do
|
174
|
+
it 'creates a new instance with each element of the array as parameters and calls #save! on it' do
|
175
|
+
args = [
|
176
|
+
{:id => 'foo', :attributes => {:one => 1}, :blobs => {'blob1.txt' => 'This is blob 1'}},
|
177
|
+
{:id => 'bar', :attributes => {:two => 2}, :blobs => {'blob2.txt' => 'This is blob 2'}}
|
178
|
+
]
|
179
|
+
|
180
|
+
new_mock1 = mock("new_mock1")
|
181
|
+
new_mock2 = mock("new_mock2")
|
182
|
+
TestEntity.should_receive(:new).with(args[0]).once.and_return(new_mock1)
|
183
|
+
TestEntity.should_receive(:new).with(args[1]).once.and_return(new_mock2)
|
184
|
+
new_mock1.should_receive(:save!)
|
185
|
+
new_mock2.should_receive(:save!)
|
186
|
+
|
187
|
+
TestEntity.create!(args)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
describe '.delete' do
|
194
|
+
|
195
|
+
it 'deletes the object with the given id from the database' do
|
196
|
+
TestEntity.create!(:id => 'monkey')
|
197
|
+
TestEntity.delete('monkey')
|
198
|
+
|
199
|
+
TestEntity.exists?('monkey').should be_false
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'also deletes blobs associated with the given object' do
|
203
|
+
id = 'Lemuridae'
|
204
|
+
TestEntity.create!(:id => id, :blobs => {:crowned => "Eulemur coronatus", :brown => "Eulemur fulvus"})
|
205
|
+
(GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'crowned')).data.should_not be_nil
|
206
|
+
(GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'brown')).data.should_not be_nil
|
207
|
+
TestEntity.delete(id)
|
208
|
+
|
209
|
+
(GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'attributes.json')).should be_nil
|
210
|
+
(GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'attributes.json')).should be_nil
|
211
|
+
|
212
|
+
(GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'crowned')).should be_nil
|
213
|
+
(GitModel.current_tree / File.join(TestEntity.db_subdir, id, 'brown')).should be_nil
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
describe '.delete_all' do
|
220
|
+
|
221
|
+
it 'deletes all objects of the same type from the database' do
|
222
|
+
TestEntity.create!(:id => 'monkey')
|
223
|
+
TestEntity.create!(:id => 'ape')
|
224
|
+
|
225
|
+
TestEntity.delete_all
|
226
|
+
TestEntity.find_all.should be_empty
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
describe '#delete' do
|
232
|
+
|
233
|
+
it 'deletes the object from the database' do
|
234
|
+
o = TestEntity.create!(:id => 'monkey')
|
235
|
+
o.delete
|
236
|
+
|
237
|
+
TestEntity.exists?('monkey').should be_false
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'freezes the object' do
|
241
|
+
o = TestEntity.create!(:id => 'monkey')
|
242
|
+
o.delete
|
243
|
+
|
244
|
+
o.frozen?.should be_true
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
describe '#find' do
|
250
|
+
|
251
|
+
#it 'can load an object from an empty subdir of db_root' do
|
252
|
+
# id = "foo"
|
253
|
+
# dir = File.join(GitModel.db_root, TestEntity.db_subdir, id)
|
254
|
+
# FileUtils.mkdir_p dir
|
255
|
+
|
256
|
+
# o = TestEntity.find(id)
|
257
|
+
# o.id.should == id
|
258
|
+
# o.attributes.should be_empty
|
259
|
+
# o.blobs.should be_empty
|
260
|
+
#end
|
261
|
+
|
262
|
+
describe 'with no commits in the repo' do
|
263
|
+
|
264
|
+
it 'raises GitModel::RecordNotFound if a record with the given id doesn\'t exist' do
|
265
|
+
lambda{TestEntity.find('missing')}.should raise_error(GitModel::RecordNotFound)
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'raises GitModel::RecordNotFound if a record with the given id doesn\'t exist' do
|
271
|
+
TestEntity.create!(:id => 'something')
|
272
|
+
lambda{TestEntity.find('missing')}.should raise_error(GitModel::RecordNotFound)
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'can load an object with attributes and no blobs' do
|
276
|
+
id = "foo"
|
277
|
+
attrs = {:one => 1, :two => 2}
|
278
|
+
TestEntity.create!(:id => id, :attributes => attrs)
|
279
|
+
|
280
|
+
o = TestEntity.find(id)
|
281
|
+
o.id.should == id
|
282
|
+
o.attributes.size.should == 2
|
283
|
+
o.attributes['one'].should == 1
|
284
|
+
o.attributes['two'].should == 2
|
285
|
+
o.blobs.should be_empty
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'can load an object with blobs and no attributes' do
|
289
|
+
id = 'foo'
|
290
|
+
blobs = {'blob1.txt' => 'This is blob 1', 'blob2' => 'This is blob 2'}
|
291
|
+
TestEntity.create!(:id => id, :blobs => blobs)
|
292
|
+
|
293
|
+
o = TestEntity.find(id)
|
294
|
+
o.id.should == id
|
295
|
+
o.attributes.should be_empty
|
296
|
+
o.blobs.size.should == 2
|
297
|
+
o.blobs["blob1.txt"].should == 'This is blob 1'
|
298
|
+
o.blobs["blob2"].should == 'This is blob 2'
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'can load an object with both attributes and blobs' do
|
302
|
+
id = 'foo'
|
303
|
+
attrs = {:one => 1, :two => 2}
|
304
|
+
blobs = {'blob1.txt' => 'This is blob 1', 'blob2' => 'This is blob 2'}
|
305
|
+
TestEntity.create!(:id => id, :attributes => attrs, :blobs => blobs)
|
306
|
+
|
307
|
+
o = TestEntity.find(id)
|
308
|
+
o.id.should == id
|
309
|
+
o.attributes.size.should == 2
|
310
|
+
o.attributes['one'].should == 1
|
311
|
+
o.attributes['two'].should == 2
|
312
|
+
o.blobs.size.should == 2
|
313
|
+
o.blobs["blob1.txt"].should == 'This is blob 1'
|
314
|
+
o.blobs["blob2"].should == 'This is blob 2'
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
describe '#find_all' do
|
320
|
+
|
321
|
+
it 'returns an array of all objects' do
|
322
|
+
TestEntity.create!(:id => 'one')
|
323
|
+
TestEntity.create!(:id => 'two')
|
324
|
+
TestEntity.create!(:id => 'three')
|
325
|
+
|
326
|
+
r = TestEntity.find_all
|
327
|
+
r.size.should == 3
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
describe '#exists?' do
|
333
|
+
|
334
|
+
it 'returns true if the record exists' do
|
335
|
+
TestEntity.create!(:id => 'one')
|
336
|
+
TestEntity.exists?('one').should be_true
|
337
|
+
end
|
338
|
+
|
339
|
+
it "returns false if the record doesn't exist" do
|
340
|
+
TestEntity.exists?('missing').should be_false
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
344
|
+
|
345
|
+
describe '#attributes' do
|
346
|
+
it 'accepts symbols or strings interchangeably as strings' do
|
347
|
+
o = TestEntity.new(:id => 'lol', :attributes => {"one" => 1, :two => 2})
|
348
|
+
o.save!
|
349
|
+
o.attributes["one"].should == 1
|
350
|
+
o.attributes[:one].should == 1
|
351
|
+
o.attributes["two"].should == 2
|
352
|
+
o.attributes[:two].should == 2
|
353
|
+
|
354
|
+
# Should also be true after reloading
|
355
|
+
o = TestEntity.find 'lol'
|
356
|
+
o.attributes["one"].should == 1
|
357
|
+
o.attributes[:one].should == 1
|
358
|
+
o.attributes["two"].should == 2
|
359
|
+
o.attributes[:two].should == 2
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
describe '#blobs' do
|
364
|
+
it 'accepts symbols or strings interchangeably as strings' do
|
365
|
+
o = TestEntity.new(:id => 'lol', :blobs => {"one" => 'this is blob 1', :two => 'this is blob 2'})
|
366
|
+
o.save!
|
367
|
+
o.blobs["one"].should == 'this is blob 1'
|
368
|
+
o.blobs[:one].should == 'this is blob 1'
|
369
|
+
o.blobs["two"].should == 'this is blob 2'
|
370
|
+
o.blobs[:two].should == 'this is blob 2'
|
371
|
+
|
372
|
+
# Should also be true after reloading
|
373
|
+
o = TestEntity.find 'lol'
|
374
|
+
o.blobs["one"].should == 'this is blob 1'
|
375
|
+
o.blobs[:one].should == 'this is blob 1'
|
376
|
+
o.blobs["two"].should == 'this is blob 2'
|
377
|
+
o.blobs[:two].should == 'this is blob 2'
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
describe 'attribute description in the class definition' do
|
382
|
+
|
383
|
+
it 'creates convenient accessor methods for accessing the attributes hash' do
|
384
|
+
o = TestEntity.new
|
385
|
+
class << o
|
386
|
+
attribute :colour
|
387
|
+
end
|
388
|
+
|
389
|
+
o.colour.should == nil
|
390
|
+
o.colour = "red"
|
391
|
+
o.colour.should == "red"
|
392
|
+
o.attributes[:colour].should == "red"
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'can set default values for attributes, with any ruby value for the default' do
|
396
|
+
o = TestEntity.new
|
397
|
+
|
398
|
+
# Change the singleton class for object o, this doesn't change the
|
399
|
+
# TestEntity class
|
400
|
+
class << o
|
401
|
+
attribute :size, :default => "medium"
|
402
|
+
attribute :shape, :default => 2
|
403
|
+
attribute :style, :default => nil
|
404
|
+
attribute :teeth, :default => {"molars" => 4, "canines" => 2}
|
405
|
+
end
|
406
|
+
|
407
|
+
o.size.should == "medium"
|
408
|
+
o.shape.should == 2
|
409
|
+
o.style.should == nil
|
410
|
+
o.teeth.should == {"molars" => 4, "canines" => 2}
|
411
|
+
|
412
|
+
o.size = "large"
|
413
|
+
o.size.should == "large"
|
414
|
+
end
|
415
|
+
|
416
|
+
end
|
417
|
+
|
418
|
+
describe 'blob description in the class definition' do
|
419
|
+
|
420
|
+
it 'creates convenient accessor methods for accessing the blobs hash' do
|
421
|
+
o = TestEntity.new
|
422
|
+
class << o
|
423
|
+
blob :avatar
|
424
|
+
end
|
425
|
+
|
426
|
+
o.avatar.should == nil
|
427
|
+
|
428
|
+
o.avatar = "image_data_here"
|
429
|
+
o.avatar.should == "image_data_here"
|
430
|
+
o.blobs[:avatar].should == "image_data_here"
|
431
|
+
end
|
432
|
+
|
433
|
+
end
|
434
|
+
|
435
|
+
end
|
436
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GitModel::Transaction do
|
4
|
+
|
5
|
+
describe '#execute' do
|
6
|
+
it "yields to a given block" do
|
7
|
+
m = mock("mock")
|
8
|
+
m.should_receive(:a_method)
|
9
|
+
GitModel::Transaction.new.execute do
|
10
|
+
m.a_method
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "when called the first time" do
|
15
|
+
it "creates a new Git index" do
|
16
|
+
index = mock("index")
|
17
|
+
index.stub!(:read_tree)
|
18
|
+
index.stub!(:commit)
|
19
|
+
Grit::Index.should_receive(:new).and_return(index)
|
20
|
+
GitModel::Transaction.new.execute {}
|
21
|
+
end
|
22
|
+
|
23
|
+
it "commits after yielding" do
|
24
|
+
index = mock("index")
|
25
|
+
index.stub!(:read_tree)
|
26
|
+
index.should_receive(:commit)
|
27
|
+
Grit::Index.should_receive(:new).and_return(index)
|
28
|
+
GitModel::Transaction.new.execute {}
|
29
|
+
end
|
30
|
+
|
31
|
+
it "can create the first commit in the repo" do
|
32
|
+
GitModel::Transaction.new.execute do |t|
|
33
|
+
t.index.add "foo", "foo"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# TODO it "locks the branch while committing"
|
38
|
+
|
39
|
+
# TODO it "merges commits from concurrent transactions"
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "when called recursively" do
|
44
|
+
|
45
|
+
it "re-uses the existing git index and doesn't commit" do
|
46
|
+
index = mock("index")
|
47
|
+
index.stub!(:read_tree)
|
48
|
+
index.should_receive(:commit).once
|
49
|
+
Grit::Index.should_receive(:new).and_return(index)
|
50
|
+
GitModel::Transaction.new.execute do |t|
|
51
|
+
t.execute {}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe GitModel do
|
4
|
+
|
5
|
+
describe "#last_commit" do
|
6
|
+
|
7
|
+
it "returns nil if there are no commits" do
|
8
|
+
GitModel.last_commit.should == nil
|
9
|
+
end
|
10
|
+
|
11
|
+
it "returns the most recent commit on a branch" do
|
12
|
+
index = Grit::Index.new(GitModel.repo)
|
13
|
+
head = GitModel.repo.commits.first
|
14
|
+
index.read_tree head.to_s
|
15
|
+
index.add "foo", "foo"
|
16
|
+
sha = index.commit nil, nil, nil, nil, 'master'
|
17
|
+
|
18
|
+
GitModel.last_commit.to_s.should == sha
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#current_tree" do
|
24
|
+
|
25
|
+
it "returns nil if there are no commits" do
|
26
|
+
GitModel.current_tree.should == nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns the tree for the most recent commit on a branch" do
|
30
|
+
last_commit = mock('last_commit')
|
31
|
+
last_commit.should_receive(:tree).and_return("yay, a tree!")
|
32
|
+
GitModel.should_receive(:last_commit).with('master').and_return(last_commit)
|
33
|
+
GitModel.current_tree('master')
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
require 'gitmodel'
|
6
|
+
|
7
|
+
Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each {|f| require f}
|
8
|
+
|
9
|
+
Rspec.configure do |c|
|
10
|
+
c.mock_with :rspec
|
11
|
+
end
|
12
|
+
|