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.
Files changed (51) hide show
  1. data/README.rdoc +795 -0
  2. data/doc/Examples.rdoc +36 -0
  3. data/doc/examples/key_value/kv_get.rb +29 -0
  4. data/doc/examples/key_value/kv_init.rb +20 -0
  5. data/doc/examples/key_value/kv_list.rb +28 -0
  6. data/doc/examples/key_value/kv_remove.rb +29 -0
  7. data/doc/examples/key_value/kv_set.rb +39 -0
  8. data/doc/examples/key_value/model.rb +156 -0
  9. data/doc/examples/key_value/test.rb +50 -0
  10. data/doc/examples/test_suite/model.rb +503 -0
  11. data/doc/examples/test_suite/test.rb +173 -0
  12. data/doc/examples/test_suite/ts_add_bug.rb +65 -0
  13. data/doc/examples/test_suite/ts_add_module.rb +74 -0
  14. data/doc/examples/test_suite/ts_add_module_to_test.rb +78 -0
  15. data/doc/examples/test_suite/ts_add_test.rb +77 -0
  16. data/doc/examples/test_suite/ts_add_test_suite.rb +65 -0
  17. data/doc/examples/test_suite/ts_add_test_to_bug.rb +76 -0
  18. data/doc/examples/test_suite/ts_init.rb +20 -0
  19. data/doc/examples/test_suite/ts_list.rb +118 -0
  20. data/doc/examples/test_suite/ts_perform_test.rb +104 -0
  21. data/doc/examples/test_suite/ts_update_bugs.rb +58 -0
  22. data/doc/examples/user_group/model.rb +265 -0
  23. data/doc/examples/user_group/test.rb +64 -0
  24. data/doc/examples/user_group/ug_add_group.rb +39 -0
  25. data/doc/examples/user_group/ug_add_group_user.rb +36 -0
  26. data/doc/examples/user_group/ug_add_user.rb +39 -0
  27. data/doc/examples/user_group/ug_init.rb +20 -0
  28. data/doc/examples/user_group/ug_list.rb +32 -0
  29. data/lib/git-ds.rb +14 -0
  30. data/lib/git-ds/config.rb +53 -0
  31. data/lib/git-ds/database.rb +289 -0
  32. data/lib/git-ds/exec_cmd.rb +107 -0
  33. data/lib/git-ds/index.rb +205 -0
  34. data/lib/git-ds/model.rb +136 -0
  35. data/lib/git-ds/model/db_item.rb +42 -0
  36. data/lib/git-ds/model/fs_item.rb +51 -0
  37. data/lib/git-ds/model/item.rb +428 -0
  38. data/lib/git-ds/model/item_list.rb +97 -0
  39. data/lib/git-ds/model/item_proxy.rb +128 -0
  40. data/lib/git-ds/model/property.rb +144 -0
  41. data/lib/git-ds/model/root.rb +46 -0
  42. data/lib/git-ds/repo.rb +455 -0
  43. data/lib/git-ds/shared.rb +17 -0
  44. data/lib/git-ds/transaction.rb +77 -0
  45. data/tests/ut_database.rb +304 -0
  46. data/tests/ut_git_grit_equiv.rb +195 -0
  47. data/tests/ut_index.rb +203 -0
  48. data/tests/ut_model.rb +360 -0
  49. data/tests/ut_repo.rb +260 -0
  50. data/tests/ut_user_group_model.rb +316 -0
  51. 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