git-ds 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +795 -0
- data/doc/Examples.rdoc +36 -0
- data/doc/examples/key_value/kv_get.rb +29 -0
- data/doc/examples/key_value/kv_init.rb +20 -0
- data/doc/examples/key_value/kv_list.rb +28 -0
- data/doc/examples/key_value/kv_remove.rb +29 -0
- data/doc/examples/key_value/kv_set.rb +39 -0
- data/doc/examples/key_value/model.rb +156 -0
- data/doc/examples/key_value/test.rb +50 -0
- data/doc/examples/test_suite/model.rb +503 -0
- data/doc/examples/test_suite/test.rb +173 -0
- data/doc/examples/test_suite/ts_add_bug.rb +65 -0
- data/doc/examples/test_suite/ts_add_module.rb +74 -0
- data/doc/examples/test_suite/ts_add_module_to_test.rb +78 -0
- data/doc/examples/test_suite/ts_add_test.rb +77 -0
- data/doc/examples/test_suite/ts_add_test_suite.rb +65 -0
- data/doc/examples/test_suite/ts_add_test_to_bug.rb +76 -0
- data/doc/examples/test_suite/ts_init.rb +20 -0
- data/doc/examples/test_suite/ts_list.rb +118 -0
- data/doc/examples/test_suite/ts_perform_test.rb +104 -0
- data/doc/examples/test_suite/ts_update_bugs.rb +58 -0
- data/doc/examples/user_group/model.rb +265 -0
- data/doc/examples/user_group/test.rb +64 -0
- data/doc/examples/user_group/ug_add_group.rb +39 -0
- data/doc/examples/user_group/ug_add_group_user.rb +36 -0
- data/doc/examples/user_group/ug_add_user.rb +39 -0
- data/doc/examples/user_group/ug_init.rb +20 -0
- data/doc/examples/user_group/ug_list.rb +32 -0
- data/lib/git-ds.rb +14 -0
- data/lib/git-ds/config.rb +53 -0
- data/lib/git-ds/database.rb +289 -0
- data/lib/git-ds/exec_cmd.rb +107 -0
- data/lib/git-ds/index.rb +205 -0
- data/lib/git-ds/model.rb +136 -0
- data/lib/git-ds/model/db_item.rb +42 -0
- data/lib/git-ds/model/fs_item.rb +51 -0
- data/lib/git-ds/model/item.rb +428 -0
- data/lib/git-ds/model/item_list.rb +97 -0
- data/lib/git-ds/model/item_proxy.rb +128 -0
- data/lib/git-ds/model/property.rb +144 -0
- data/lib/git-ds/model/root.rb +46 -0
- data/lib/git-ds/repo.rb +455 -0
- data/lib/git-ds/shared.rb +17 -0
- data/lib/git-ds/transaction.rb +77 -0
- data/tests/ut_database.rb +304 -0
- data/tests/ut_git_grit_equiv.rb +195 -0
- data/tests/ut_index.rb +203 -0
- data/tests/ut_model.rb +360 -0
- data/tests/ut_repo.rb +260 -0
- data/tests/ut_user_group_model.rb +316 -0
- metadata +142 -0
data/README.rdoc
ADDED
@@ -0,0 +1,795 @@
|
|
1
|
+
A gem providing a Git-backed datastore. This acts as a version-controlled
|
2
|
+
hierarchical data store.
|
3
|
+
|
4
|
+
Requires Grit[http://grit.rubyforge.org].
|
5
|
+
|
6
|
+
==Usage:
|
7
|
+
|
8
|
+
require 'git-ds'
|
9
|
+
|
10
|
+
# connect to data model
|
11
|
+
db = GitDS::Database.connect('/path/to/repo.db')
|
12
|
+
model = GitDS::Model.new(db)
|
13
|
+
|
14
|
+
# store item in database
|
15
|
+
model.add_item('path/to/item', 'data in item')
|
16
|
+
|
17
|
+
# test for existence of item
|
18
|
+
model.include? 'path/to/item'
|
19
|
+
|
20
|
+
# list items in database
|
21
|
+
puts model.list_children
|
22
|
+
puts model.list_children('path/to')
|
23
|
+
|
24
|
+
# update item in database
|
25
|
+
model.add_item('path/to/item', 'revised data in item')
|
26
|
+
|
27
|
+
# retrieve item from database
|
28
|
+
data = model.get_item('path/to/item')
|
29
|
+
|
30
|
+
# delete item from database
|
31
|
+
model.delete_item('path/to/item')
|
32
|
+
|
33
|
+
# close database connection
|
34
|
+
db.close
|
35
|
+
|
36
|
+
See {Examples}[link:doc/Examples_rdoc.html].
|
37
|
+
|
38
|
+
==Data Model
|
39
|
+
|
40
|
+
The recommended way to use GitDS is with a data model.
|
41
|
+
|
42
|
+
A subclass of GitDS::Model is used to define the data model. Note that the
|
43
|
+
name of a Model subclass determines the top-level directory in the Git
|
44
|
+
repository that will contain the data for the Model.
|
45
|
+
|
46
|
+
The structure for a GitDS::Model will have a subdirectory for each class which
|
47
|
+
contains instances of that class. The following repository structure shows
|
48
|
+
the model 'my_model' which contains two data types ('my_class' and
|
49
|
+
'another class', which have 3 instances and 2 instances respectively:
|
50
|
+
|
51
|
+
my_model/my_class/1/...
|
52
|
+
my_model/my_class/2/...
|
53
|
+
my_model/my_class/3/...
|
54
|
+
my_model/another class/1/...
|
55
|
+
my_model/another class/2/...
|
56
|
+
|
57
|
+
In a GitDS::Model, leaf nodes (files) contain actual data, while classes,
|
58
|
+
instances, and members appear as directories. A model 'Stuff' with an
|
59
|
+
instance of the class MyClass with ident 'foo' and the members (x=100,
|
60
|
+
comment='factor of 10') would appear in the repository as follows:
|
61
|
+
|
62
|
+
Stuff/MyClass/foo/x : file containing '100'
|
63
|
+
Stuff/MyClass/foo/comment : file containing 'factor of 10'
|
64
|
+
|
65
|
+
See the examples KeyValueModel, TestSuiteModel, and UserGroupModel for
|
66
|
+
demonstrations of using data models.
|
67
|
+
|
68
|
+
===Model Items
|
69
|
+
|
70
|
+
Items stored in the model are subclasses of GitDS::ModelItem or
|
71
|
+
GitDS::FsModelItem. Note that all GitDS::ModelIten objects must invoke the
|
72
|
+
GitDS::ModelItemClass#name method in their class definition:
|
73
|
+
|
74
|
+
class DbThing < GitDS::ModelItem
|
75
|
+
# name of class (and of subdirectory where class instances appear)
|
76
|
+
name 'thing'
|
77
|
+
# ...
|
78
|
+
end
|
79
|
+
|
80
|
+
To wrap an existing class hierarchy in ModelItems (e.g. in an ORM), use the
|
81
|
+
ModelItem modules instead of subclassing:
|
82
|
+
|
83
|
+
# DB-only object
|
84
|
+
class DbThing < Thing
|
85
|
+
extend GitDS::ModelItemClass
|
86
|
+
include GitDS::ModelItemObject
|
87
|
+
|
88
|
+
name 'thing'
|
89
|
+
end
|
90
|
+
|
91
|
+
# FS and DB object
|
92
|
+
class DbThing < Thing
|
93
|
+
extend GitDS::FsModelItemClass
|
94
|
+
include GitDS::FsModelItemObject
|
95
|
+
|
96
|
+
name 'thing'
|
97
|
+
end
|
98
|
+
|
99
|
+
===ModelItem vs FsModelItem
|
100
|
+
|
101
|
+
When using DB-only GitDS::ModelItems, the working directory will ALWAYS
|
102
|
+
be missing files. This means that commits and such should only be done from
|
103
|
+
with the GitDS repo or database entries WILL BE DELETED when command-line
|
104
|
+
tools are run. To avoid this problem, use only GitDS::FsModelItem classes.
|
105
|
+
|
106
|
+
===Root Items
|
107
|
+
|
108
|
+
All items in a model are children of the model's root item.
|
109
|
+
|
110
|
+
The root GitDS::ModelItem for a GitDS::Model can be accessed through
|
111
|
+
GitDS::Model#root.
|
112
|
+
|
113
|
+
===Properties
|
114
|
+
|
115
|
+
GitDS::ModelItem objects can define properties (PropertyDefinition objects)
|
116
|
+
using the GitDS::ModelItemClass#property method:
|
117
|
+
|
118
|
+
class DbThing < ModelItem
|
119
|
+
name 'thing'
|
120
|
+
|
121
|
+
property :foo
|
122
|
+
|
123
|
+
# 'bar' property defaults to false
|
124
|
+
property :bar, false
|
125
|
+
|
126
|
+
# 'baz' property is validated to ensure it is an integer
|
127
|
+
property(:baz, 0) { |val| val.to_s.to_i == val }
|
128
|
+
end
|
129
|
+
|
130
|
+
Properties can be accessed using GitDS::ModelItemObject#property and
|
131
|
+
GitDS::ModelItemObject#set_property :
|
132
|
+
|
133
|
+
class DbThing < ModelItem
|
134
|
+
name 'thing'
|
135
|
+
|
136
|
+
property :foo
|
137
|
+
|
138
|
+
def foo
|
139
|
+
property(:foo)
|
140
|
+
end
|
141
|
+
|
142
|
+
def foo=(val)
|
143
|
+
set_property(:foo, val)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
Properties are stored as Strings; any object supporting to_s can be stored
|
148
|
+
in a property. When reading a property, the String value is returned unless
|
149
|
+
one of the special accessor methods is used:
|
150
|
+
|
151
|
+
class DbThing < ModelItem
|
152
|
+
name 'thing'
|
153
|
+
|
154
|
+
property :foo
|
155
|
+
|
156
|
+
# Access foo as String
|
157
|
+
def foo
|
158
|
+
property(:foo)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Access foo as an Integer
|
162
|
+
def foo_to_i
|
163
|
+
integer_property(:foo)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Access foo as a Float
|
167
|
+
def foo_to_f
|
168
|
+
float_property(:foo)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Access foo as a Bool
|
172
|
+
def foo_to_b
|
173
|
+
bool_property(:foo)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Access foo as a Time
|
177
|
+
def foo_to_ts
|
178
|
+
ts_property(:foo)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Access foo as an Array
|
182
|
+
def foo_to_a
|
183
|
+
array_property(:foo)
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
===ModelItem Initialization
|
189
|
+
|
190
|
+
GitDS::ModelItem objects are created in two stages.
|
191
|
+
|
192
|
+
First, they are created in the repository using GitDS::ModelItemClass#create.
|
193
|
+
This takes a parent object and a Hash of arguments, and invokes
|
194
|
+
GitDS::ModelItemClass#fill to generate the ModelItem subtree in the repository.
|
195
|
+
The default implementation of GitDS::ModelItemClass#fill creates leaf nodes for
|
196
|
+
all properties supplied in the args Hash, but it can be subclassed to create
|
197
|
+
additional children:
|
198
|
+
|
199
|
+
def self.fill(model, item_path, args)
|
200
|
+
super
|
201
|
+
# fill the :created property instead of using an args field
|
202
|
+
properties[:created].set(model, item_path, Time.now.to_s)
|
203
|
+
end
|
204
|
+
|
205
|
+
def initialize(model, path)
|
206
|
+
super
|
207
|
+
# initialize other class members
|
208
|
+
@local_stuff = []
|
209
|
+
end
|
210
|
+
|
211
|
+
===Item Lists
|
212
|
+
|
213
|
+
A GitDS::ModelItem may have one or more instances of another GitDS::ModelItem
|
214
|
+
as its children. For example, a CompanyModelItem will have any number of
|
215
|
+
EmployeeModelItem children. In this case, the child GitDS::ModelItems are
|
216
|
+
defined in a GitDS::ModelItem class subtree:
|
217
|
+
|
218
|
+
company/ACME Inc/employee/First Guy
|
219
|
+
company/ACME Inc/employee/Second Guy
|
220
|
+
company/ACME Inc/employee/Third Guy
|
221
|
+
company/Fools-R-Us/employee/A Fool
|
222
|
+
company/Fools-R-Us/employee/Mo Foolz
|
223
|
+
company/Fools-R-Us/employee/Max Fool
|
224
|
+
|
225
|
+
In the above repository, the instances of the CompanyModelItem class
|
226
|
+
('ACME Inc', 'Fools-R-Us') have an EmployeeModelItem class directory in
|
227
|
+
which their EmployeeModelItem children (['First Guy', 'Second Guy', 'Third Guy']
|
228
|
+
or ['A Fool', 'Mo Foolz', 'Max Fool']) are stored. These subdirectories of
|
229
|
+
GitDS::ModelItem class instances are examples of a GitDS::ModelItemList.
|
230
|
+
|
231
|
+
A GitDS::ModelItemList is instantiated in the constructor of a GitDS::ModelItem:
|
232
|
+
|
233
|
+
def initialize(mode, path)
|
234
|
+
super
|
235
|
+
@emp = GitDS::ModelItemList.new(EmployeeModelItem, model, path)
|
236
|
+
end
|
237
|
+
|
238
|
+
The items in the list can then be wrapped with accessors:
|
239
|
+
|
240
|
+
def employees
|
241
|
+
ensure_valid
|
242
|
+
@emp.keys
|
243
|
+
end
|
244
|
+
|
245
|
+
def employee(ident)
|
246
|
+
ensure_valid
|
247
|
+
@emp[ident]
|
248
|
+
end
|
249
|
+
|
250
|
+
def add_employee(e)
|
251
|
+
ensure_valid
|
252
|
+
@emp.add(self, { :ident => e.ident, :name => e.name })
|
253
|
+
end
|
254
|
+
|
255
|
+
def del_employee(ident)
|
256
|
+
ensure_valid
|
257
|
+
@emp.delete(ident)
|
258
|
+
end
|
259
|
+
|
260
|
+
This hides the GitDS::ModelItemList behind an interface so that the ModelItems
|
261
|
+
behave as normal object children:
|
262
|
+
|
263
|
+
# use existing customer at c_path
|
264
|
+
c = CustomerModelItem.new(model, c_path)
|
265
|
+
|
266
|
+
# use existing employee at e_path
|
267
|
+
e = EmployeeModelItem.new(model, e_path)
|
268
|
+
|
269
|
+
c.add_employee(e)
|
270
|
+
c.employees.each { |e| puts e.inspect }
|
271
|
+
puts c.employee(e.ident).inspect
|
272
|
+
|
273
|
+
e.del_employee(e.ident)
|
274
|
+
|
275
|
+
Note: GitDS::ModelItemList uses the name of the class as the name of the
|
276
|
+
subdirectory in which items are stored in the repo. To change this behavior
|
277
|
+
(for example, if a GitDS::ModelItem has two different lists of the same class
|
278
|
+
of GitDS::ModelItem objects), subclass the GitDS::ModelItem in the list and
|
279
|
+
give it a different name.
|
280
|
+
|
281
|
+
===Proxy Items
|
282
|
+
|
283
|
+
A ModelItem may have a member which refers to another ModelItem which it does
|
284
|
+
not necessarily 'own'. For example, EmployeeModelItem might have the member
|
285
|
+
'boss' which refers to another EmployeeModelItem.
|
286
|
+
|
287
|
+
In such cases, the member is a Proxy for another GitDS::ModelItem. In the repo,
|
288
|
+
a Proxy is a BLOB which contains the path to a GitDS::ModelItem instance.
|
289
|
+
|
290
|
+
The GitDS::ModelItemClass#link_property method is used to define a
|
291
|
+
property that is a Proxy for another GitDS::ModelItem. The method takes a
|
292
|
+
property identifier (String or Symbol) and the GitDS::ModelItem class being
|
293
|
+
linked to:
|
294
|
+
|
295
|
+
link_property(:name, GitDS::ModelItem)
|
296
|
+
|
297
|
+
Note that the Property is a proxy for a *class*. Internally, this is
|
298
|
+
implemented as an instance of GitDS::ModelItemClassProxy, which associates
|
299
|
+
a named property (i.e. a path to a BLOB in the repo that contains the link
|
300
|
+
data) with a GitDS::ModelItem class. This class is used to instantiate the
|
301
|
+
GitDS::ModelItem from the path stored in the property.
|
302
|
+
|
303
|
+
===Proxy Item Lists
|
304
|
+
|
305
|
+
A ModelItem may have a list of member ModelItems that it does it does not
|
306
|
+
actually own. For example, a MeetingModelItem may have the member 'attendees'
|
307
|
+
which is a list of EmployeeModelItem objects.
|
308
|
+
|
309
|
+
Such a list is a ProxyItemList.
|
310
|
+
|
311
|
+
def initialize(model, path)
|
312
|
+
super
|
313
|
+
@attn = GitDS::ProxyItemList.new(EmployeeModelItem, model, path)
|
314
|
+
end
|
315
|
+
|
316
|
+
def attendees
|
317
|
+
ensure_valid
|
318
|
+
@attn.keys
|
319
|
+
end
|
320
|
+
|
321
|
+
def attendee(ident)
|
322
|
+
ensure_valid
|
323
|
+
@attn[ident]
|
324
|
+
end
|
325
|
+
|
326
|
+
def add_attendee(obj)
|
327
|
+
ensure_valid
|
328
|
+
@attn.add(self, obj)
|
329
|
+
end
|
330
|
+
|
331
|
+
def del_attendee(ident)
|
332
|
+
ensure_valid
|
333
|
+
@attn.delete(ident)
|
334
|
+
end
|
335
|
+
|
336
|
+
Note: The ProxyItemList is based on ModelItemList, and uses the name of the
|
337
|
+
proxied class as the subdirectory in which the links are stored in the repo.
|
338
|
+
|
339
|
+
===Reducing Commits
|
340
|
+
|
341
|
+
By default, GitDS writes a commit every time that a GitDS::ModelItem is
|
342
|
+
created, modified, or deleted. This can lead to a huge number of commits,
|
343
|
+
which inflate the database and have an impact on performance.
|
344
|
+
|
345
|
+
To cut down on the number of commits, wrap all work in an GitDS::ExecCmd or a
|
346
|
+
GitDS::Transaction:
|
347
|
+
|
348
|
+
model.exec {
|
349
|
+
...
|
350
|
+
}
|
351
|
+
model.transaction {
|
352
|
+
...
|
353
|
+
}
|
354
|
+
|
355
|
+
All work performed in a model is implicitly wrapped in an GitDS::ExecCmd. These
|
356
|
+
commands can be nested, with a commit only occurring when the outermost
|
357
|
+
command completes. See and GitDS::ExecCmd and GitDs::Transaction.
|
358
|
+
|
359
|
+
In order to perform all work in a branch which gets automatically merged, use
|
360
|
+
GitDS::Model#branched_transaction:
|
361
|
+
|
362
|
+
model.branched_transaction('version1.9') {
|
363
|
+
...
|
364
|
+
}
|
365
|
+
|
366
|
+
See GitDS::Database#branch_and_merge for more details. The TestSuiteModel
|
367
|
+
example provides an example of using commands, transactions, and branches.
|
368
|
+
|
369
|
+
===Direct Model Access
|
370
|
+
|
371
|
+
In addition to the GitDS::ModelItem classes, the contents of a GitDS::Model
|
372
|
+
can be accessed directly:
|
373
|
+
|
374
|
+
# does model include the file 'class/id/property'?
|
375
|
+
model.include?('class/id/property')
|
376
|
+
|
377
|
+
# list the contents of the model root
|
378
|
+
model.list_children
|
379
|
+
|
380
|
+
# list the contents of the 'class/id' directory
|
381
|
+
model.list_children('class/id')
|
382
|
+
|
383
|
+
# Set the contents of the BLOB 'class/id/property' to value
|
384
|
+
model.add_item('class/id/property', 'value)
|
385
|
+
|
386
|
+
# As above, but also create an entry on the filesystem for the BLOB.
|
387
|
+
model.add_fs_item('class/id/property', 'value)
|
388
|
+
|
389
|
+
# Get the contents of the BLOB 'class/id/property'
|
390
|
+
model.get_item('class/id/property')
|
391
|
+
|
392
|
+
# Remove 'class/id/property' from the repository.
|
393
|
+
model.delete_item('class/id/property')
|
394
|
+
|
395
|
+
Finally, the GitDS::Database instance for the model can be accessed through
|
396
|
+
GitDS::Model#db.
|
397
|
+
|
398
|
+
=== Model-level Classes
|
399
|
+
|
400
|
+
* GitDS::Model
|
401
|
+
* GitDS::ModelItem
|
402
|
+
* GitDS::RootItem
|
403
|
+
* GitDS::FsModelItem
|
404
|
+
* GitDS::PropertyDefinition
|
405
|
+
* GitDS::ModelItemList
|
406
|
+
* GitDS::ModelItemClassProxy
|
407
|
+
* GitDS::ProxyProperty
|
408
|
+
* GitDS::ProxyItemList
|
409
|
+
|
410
|
+
|
411
|
+
==Database Access
|
412
|
+
|
413
|
+
The GitDS::Database class is a subclass of GitDS::Repo; all of the methods
|
414
|
+
of GitDS::Repo are made available.
|
415
|
+
|
416
|
+
In the GitDS API, GitDS::Database is considered to be a database connection.
|
417
|
+
A GitDS::Database instance has a single Staging Index that is used by all of
|
418
|
+
its callers. For this reason, it is not recommended that a single
|
419
|
+
GitDS::Database instance be used across multiple threads.
|
420
|
+
|
421
|
+
===Actor
|
422
|
+
|
423
|
+
The author associated with commits to the Git repo. See Grit::Actor.
|
424
|
+
|
425
|
+
# Set the Database actor to Grit::Actor.new(name, email)
|
426
|
+
db.set_author(name, email)
|
427
|
+
|
428
|
+
actor = db.actor
|
429
|
+
db.actor=(Grit::Actor.new(name, email))
|
430
|
+
|
431
|
+
===Connecting to a Database
|
432
|
+
|
433
|
+
To open a GitDS::Database, use the connect() class method. The 'path'
|
434
|
+
argument is a path to the root of the Git repository, and 'autocreate'
|
435
|
+
will cause a Git repository to be created if it is set (and if the repository
|
436
|
+
does not already exist). Note that 'autocreate' is true by default.
|
437
|
+
|
438
|
+
db = GitDS::Database.connect('my_stuff.db')
|
439
|
+
|
440
|
+
To connect to a GitDS::Database as a specific user (instead of using the default
|
441
|
+
values in .git/config), use the connect_as() class method:
|
442
|
+
|
443
|
+
db = GitDS::Database.connect_as('test.db', 'hank', 'hk@users.net')
|
444
|
+
|
445
|
+
Closing the database will set the 'stale' flag, and cause most subsequent
|
446
|
+
database operations to fail.
|
447
|
+
|
448
|
+
db.close
|
449
|
+
|
450
|
+
===Executing DB Operations
|
451
|
+
|
452
|
+
Series of database operations can be enclosed in a block sent to
|
453
|
+
GitDS::Database#exec. This creates a GitDS::ExecCmd object, which performs
|
454
|
+
a commit after the block has been executed. GitDS::ExecCmd is therefore a
|
455
|
+
useful way to group a block of work into a single commit. Note that the
|
456
|
+
GitDS::Database connection and its Stage Index are accessible inside the
|
457
|
+
block via the database and index methods.
|
458
|
+
|
459
|
+
db.exec {
|
460
|
+
database.list('files').each do |name|
|
461
|
+
...
|
462
|
+
end
|
463
|
+
}
|
464
|
+
|
465
|
+
db.exec {
|
466
|
+
# override the default commit author and message
|
467
|
+
author 'Guy', 'guy@people.org'
|
468
|
+
message 'Added one file'
|
469
|
+
|
470
|
+
...
|
471
|
+
index.add('files/1', '111111')
|
472
|
+
}
|
473
|
+
|
474
|
+
Note that the block is executed via instance_eval, so every method of the
|
475
|
+
GitDS::ExecCmd object is available to the block. The use of instance_eval can
|
476
|
+
have unexpected side effects if GitDS#exec is called from within a method of
|
477
|
+
a class instance: the instance methods and members for the calling class
|
478
|
+
are no longer accessible, and must declared in the body of the method calling
|
479
|
+
the exec.
|
480
|
+
|
481
|
+
class Stuff
|
482
|
+
attr_accessor :path
|
483
|
+
|
484
|
+
def wrong_way(val)
|
485
|
+
db.exec { database.add(path, val) }
|
486
|
+
end
|
487
|
+
|
488
|
+
def right_way(val)
|
489
|
+
path = self.path
|
490
|
+
db.exec { database.add(path, val) }
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
Database commands can be nested. When nested, a commit is only performed
|
495
|
+
when the outermost command has been executed.
|
496
|
+
|
497
|
+
db.exec {
|
498
|
+
...
|
499
|
+
db.exec {
|
500
|
+
...
|
501
|
+
# no commit happens here
|
502
|
+
}
|
503
|
+
...
|
504
|
+
# commit happens here
|
505
|
+
}
|
506
|
+
|
507
|
+
Note that GitDS::ExecCmd uses GitDS::Database#staging to determine nesting.
|
508
|
+
When GitDS::Database#exec is called, a Stage Index is created in the database
|
509
|
+
if none exists. As long as a Stage Index exists, a GitDS::ExecCmd object will
|
510
|
+
assume it is nested, and therefore will not perform an index.build or a
|
511
|
+
commit after the code block has executed.
|
512
|
+
|
513
|
+
===Transactions
|
514
|
+
|
515
|
+
A GitDS::Transaction is a GitDS::ExecCmd object that ensures the block
|
516
|
+
completes execution before a commit is performed. If the block executes without
|
517
|
+
raising an exception, a commit is performed; otherwise, all changes are
|
518
|
+
discarded.
|
519
|
+
|
520
|
+
db.transaction {
|
521
|
+
index.add('files/1', '111111')
|
522
|
+
|
523
|
+
# override default commit author and message
|
524
|
+
author 'A Developer', 'dev@example.com'
|
525
|
+
message '[ADEV] Fixed bug in wossname'
|
526
|
+
}
|
527
|
+
|
528
|
+
A GitDS::Transaction can be aborted with the rollback method, which raises
|
529
|
+
a GitDS::TransactionRollback exception. This will cause the transaction, and
|
530
|
+
all enclosing transactions, to be aborted.
|
531
|
+
|
532
|
+
# rollback the transaction
|
533
|
+
db.transaction {
|
534
|
+
...
|
535
|
+
rollback if not obj.some_complex_operation(data)
|
536
|
+
...
|
537
|
+
}
|
538
|
+
|
539
|
+
By default, all exceptions are caught by the GitDS::Transaction. This can make
|
540
|
+
debugging difficult, as application errors will not be detected by the calling
|
541
|
+
code. In order to prevent a GitDS::Transaction from discarding exceptions,
|
542
|
+
invoke the GitDS::Transaction#propagate method in the body of the code block:
|
543
|
+
|
544
|
+
# re-raise all non-rollback exceptions
|
545
|
+
db.transaction {
|
546
|
+
propagate
|
547
|
+
...
|
548
|
+
}
|
549
|
+
|
550
|
+
As with GitDS::Database#exec, invocations of GitDS::Database#transaction can
|
551
|
+
be nested, with commits only being performed in the outermost transaction.
|
552
|
+
|
553
|
+
db.transaction {
|
554
|
+
...
|
555
|
+
db.transaction {
|
556
|
+
...
|
557
|
+
# no commit is performed
|
558
|
+
}
|
559
|
+
...
|
560
|
+
# commit is performed
|
561
|
+
}
|
562
|
+
|
563
|
+
Both GitDS::Transaction and GitDS::ExecCmd use the Stage Index to detect
|
564
|
+
nesting; therefore, invoking GitDS::Database#transaction from within
|
565
|
+
GitDS::Database#exec and invoking GitDS::Database#exec from within
|
566
|
+
GitDS::Database#transaction are considered "nesting".
|
567
|
+
|
568
|
+
===Managing data
|
569
|
+
|
570
|
+
Objects in a GitDS::Database can be modified directly using GitDS::Database#add
|
571
|
+
and GitDS::Database#delete. These use GitDS::Database#exec in order to suppress
|
572
|
+
a commit if a Stage Index already exists.
|
573
|
+
|
574
|
+
# Set the contents of the BLOB 'things/mine' to 'abcdef'
|
575
|
+
db.add('things/mine', 'abcdef')
|
576
|
+
|
577
|
+
# Get the contents of the BLOB 'things/mine'
|
578
|
+
str = db.delete('things/mine')
|
579
|
+
|
580
|
+
# Get the Grit::Tree object for 'stuff/' in 'master'
|
581
|
+
t = db.tree('master', ['stuff/'])
|
582
|
+
|
583
|
+
===Branch-and-merge
|
584
|
+
|
585
|
+
GitDS::Database supports branching of code blocks via
|
586
|
+
GitDS::Database#branch_and_merge, which takes a branch tag and an author as
|
587
|
+
arguments. If a tag is not specified, one will be generated from
|
588
|
+
GitDS::Repo#last_branch_tag.
|
589
|
+
|
590
|
+
db.branch_and_merge('0.1.0-pre-alpha') {
|
591
|
+
...
|
592
|
+
}
|
593
|
+
|
594
|
+
db.branch_and_merge('0.1.1', Grit::Actor.new('A Coder')) {
|
595
|
+
...
|
596
|
+
}
|
597
|
+
|
598
|
+
This will create a new branch with the given tag using
|
599
|
+
GitDS::Database#create_branch, perform the code block using
|
600
|
+
GitDS::Database#transaction, then switch to the default branch ('master') and
|
601
|
+
merge the created branch with GitDS::Database#merge_branch. Note that the
|
602
|
+
Stage Index is saved before the branch is created and restored after it is
|
603
|
+
merged.
|
604
|
+
|
605
|
+
===Tagging the latest commit
|
606
|
+
|
607
|
+
The latest commit for the GitDS::Database can be tagged using the
|
608
|
+
GitDS::Database#mark :
|
609
|
+
|
610
|
+
# Set tag for latest commit
|
611
|
+
db.mark('v.0.0.9-alpha')
|
612
|
+
|
613
|
+
This will tag latest commit as 'v_0.0.9-alpha'.
|
614
|
+
|
615
|
+
===Database-level Classes
|
616
|
+
|
617
|
+
* GitDS::Database
|
618
|
+
* GitDS::ExecCmd
|
619
|
+
* GitDS::Transaction
|
620
|
+
|
621
|
+
==Repository Access
|
622
|
+
|
623
|
+
The lowest level of access provided by GitDS is the Repository-level. Any
|
624
|
+
lower than this and you're using Grit objects or Git utilities.
|
625
|
+
|
626
|
+
===Accessing the Index
|
627
|
+
|
628
|
+
A GitDS::Index for the repository can be created using GitDS::Repo#index_new:
|
629
|
+
|
630
|
+
idx = db.index_new
|
631
|
+
...
|
632
|
+
idx.commit('stuff done')
|
633
|
+
|
634
|
+
Note that this is a Grit::Index with some helper methods added. Se below for
|
635
|
+
details on using a proper Staging Index.
|
636
|
+
|
637
|
+
===Staging
|
638
|
+
|
639
|
+
The GitDS::Repo object provides a Git-style Staging Index in order to
|
640
|
+
emulate the Git command-line utilities. This index is cached and used
|
641
|
+
by all methods that query or modify the repository. See GitDS::StageIndex.
|
642
|
+
|
643
|
+
# get Staging Index, creating one if necessary
|
644
|
+
idx = db.staging
|
645
|
+
|
646
|
+
# set Staging Index to existing GitDS::StageIndex object
|
647
|
+
idx = GitDS::StageIndex.new(db)
|
648
|
+
db.staging = idx
|
649
|
+
|
650
|
+
# return true if a Staging Index exists
|
651
|
+
db.staging?
|
652
|
+
|
653
|
+
# delete the staging index
|
654
|
+
db.unstage
|
655
|
+
# alternative:
|
656
|
+
db.staging = nil
|
657
|
+
|
658
|
+
# perform work using the staging index
|
659
|
+
db.stage { |idx|
|
660
|
+
...
|
661
|
+
}
|
662
|
+
|
663
|
+
# perform work using the staging index and commit when done
|
664
|
+
db.stage_and_commit('work done') { |idx|
|
665
|
+
...
|
666
|
+
}
|
667
|
+
|
668
|
+
The Staging Index is used by GitDS::ExecCmd and GitDS::Transaction to
|
669
|
+
determine if they are nested. If a Staging Index exists when entering a
|
670
|
+
command or a transaction, no commit is performed then the command or
|
671
|
+
transaction exits.
|
672
|
+
|
673
|
+
===Managing data
|
674
|
+
|
675
|
+
The contents of the repository can be managed directly using low-level
|
676
|
+
methods:
|
677
|
+
|
678
|
+
# does repo include the file 'stuff/thing'?
|
679
|
+
db.include? 'stuff/thing'
|
680
|
+
|
681
|
+
# Set the contents of the BLOB 'stuff/thing' to '1234'
|
682
|
+
db.add('stuff/thing', '1234')
|
683
|
+
|
684
|
+
# Get the contents of the BLOB ''stuff/thing''
|
685
|
+
str = db.object_data('stuff/thing')
|
686
|
+
|
687
|
+
# Remove 'stuff/thing' from the repository.
|
688
|
+
db.delete('stuff/thing')
|
689
|
+
|
690
|
+
# Get the Grit::Tree object for 'stuff/' in 'master'
|
691
|
+
t = db.tree('master', ['stuff/'])
|
692
|
+
|
693
|
+
# Return the raw (git cat-file) contents of 'stuff/', recursing subtrees
|
694
|
+
str = db.raw_tree('stuff', true)
|
695
|
+
|
696
|
+
# Return a Hash with the contents of 'stuff'. Each key is a filename,
|
697
|
+
# each value is a Grit::Tree or a Grit::Blob.
|
698
|
+
h = db.list('stuff')
|
699
|
+
|
700
|
+
# Return a Hash of the subtrees (Grit::Tree values) in 'stuff'
|
701
|
+
db.list_trees(path)
|
702
|
+
|
703
|
+
# Return a Hash of the files (Grit::Blob values) in 'stuff'
|
704
|
+
db.list_blobs(path)
|
705
|
+
|
706
|
+
Where applicable, these wrap the underlying Grit::Repo methods.
|
707
|
+
|
708
|
+
===Branch and Merge
|
709
|
+
|
710
|
+
A Git branch can be created by specifying a tag name and the SHA of the commit
|
711
|
+
preceding the branch. By default, the latest commit in 'master' is used. If
|
712
|
+
a tag is not specified, one will be generated from GitDS::Repo#last_branch_tag.
|
713
|
+
Note that the final (clean) tag name is returned.
|
714
|
+
|
715
|
+
cmt = self.commits.first
|
716
|
+
name = db.create_branch('1.0.rc-4', cmt.id)
|
717
|
+
|
718
|
+
To switch to a branch, invoke GitDS::Repo#set_branch with the tag name:
|
719
|
+
|
720
|
+
# 'master'
|
721
|
+
puts db.current_branch
|
722
|
+
db.set_branch(name)
|
723
|
+
# '1.0.rc-4'
|
724
|
+
puts db.current_branch
|
725
|
+
|
726
|
+
A branch is merged to the default branch ('master') using branch_merge:
|
727
|
+
|
728
|
+
db.merge_branch(name, actor)
|
729
|
+
|
730
|
+
===Tags
|
731
|
+
|
732
|
+
Any object can be tagged by invoking GitDS::Repo#tag_object on its SHA:
|
733
|
+
|
734
|
+
db.tag_object('Current State', self.commits.first.id)
|
735
|
+
|
736
|
+
===Git access
|
737
|
+
|
738
|
+
The path of the top-level directory in the Git repository for the GitDS::Repo
|
739
|
+
can be obtained through GitDS::Repo#top_level.
|
740
|
+
|
741
|
+
Commands can be run in the underlying Git repository:
|
742
|
+
|
743
|
+
# Execute block in top-level directory of Git repo
|
744
|
+
db.exec_in_git_dir(&block)
|
745
|
+
|
746
|
+
# Another way to get db.top_level
|
747
|
+
dir = db.exec_in_git_dir { `git rev-parse --show-toplevel` }
|
748
|
+
|
749
|
+
# Create array of paths in repo
|
750
|
+
files = db.exec_in_git_dir { `git ls-files` }.split("\n")
|
751
|
+
|
752
|
+
# Execute 'command' in top-level directory of Git repo as user
|
753
|
+
db.exec_git_cmd(command, Grit::Actor.new(name, email))
|
754
|
+
|
755
|
+
# Commit all changed files as user 'A Developer'
|
756
|
+
db.exec_git_cmd("git commit -a 'Done.'",
|
757
|
+
Grit::Actor.new('A Developer', 'a@developer.net'))
|
758
|
+
|
759
|
+
# Another way to create array of paths in repo
|
760
|
+
files = db.exec_git_cmd('git ls-files').split("\n")
|
761
|
+
|
762
|
+
===Repository-level Classes
|
763
|
+
|
764
|
+
* GitDS::Repo
|
765
|
+
* GitDS::RepoConfig
|
766
|
+
* GitDS::Index
|
767
|
+
* GitDS::StageIndex
|
768
|
+
|
769
|
+
==Support for Git features
|
770
|
+
|
771
|
+
* Branch : Supported (see GitDS::Database#branch_and_merge)
|
772
|
+
* Tag : Supported (see GitDS::Database#mark)
|
773
|
+
* Merge : Supported (see GitDS::Database#branch_and_merge).
|
774
|
+
* Remote : Unsupported; use command-line tools.
|
775
|
+
* Revert : Unsupported; use command-line tools.
|
776
|
+
|
777
|
+
==Rationale
|
778
|
+
|
779
|
+
The module is intended to manage the mundane data access for a git object
|
780
|
+
database by providing standard database CRUD operations.
|
781
|
+
|
782
|
+
The notion of a Database and a Data Model were introduced to hide the
|
783
|
+
complexity of using the Git object database as a backend.
|
784
|
+
|
785
|
+
More sophisticated manipulation of the repository must be performed using the
|
786
|
+
Git toolchain.
|
787
|
+
|
788
|
+
Note: This is not a relational or an ACID-compliant database, and was never
|
789
|
+
intended to be.
|
790
|
+
|
791
|
+
===Why Git?
|
792
|
+
|
793
|
+
* object database is content-addressable
|
794
|
+
* version control is free
|
795
|
+
* merging of databases is free
|