git-ds 0.9.2

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