git-ds 0.9.2
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.
- 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
|