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
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env ruby
2
+ # :title: Git-DS::ModelItemList
3
+ =begin rdoc
4
+
5
+ Copyright 2011 Thoughtgang <http://www.thoughtgang.org>
6
+ =end
7
+
8
+ module GitDS
9
+
10
+ =begin rdoc
11
+ A generic list of ModelItem objects.
12
+
13
+ This associates a ModelItem class with a model and a base path in that model.
14
+ All elements in this list are subdirectories of the base path, and will be
15
+ instantiated/created/listed using methods in the ModelItem class.
16
+
17
+ This is used for ModelItem children of a ModelItem object (NOT Property
18
+ children).
19
+ =end
20
+ class ModelItemList
21
+ include Enumerable
22
+
23
+ def initialize(cls, model, path)
24
+ @item_class = cls
25
+ @model = model
26
+ @base_path = path
27
+ end
28
+
29
+ =begin rdoc
30
+ List ModelItem class instances contained in this list.
31
+
32
+ Note: This always returns a sorted list.
33
+ =end
34
+ def keys
35
+ @item_class.list_in_path(@model, @base_path)
36
+ end
37
+
38
+ =begin rdoc
39
+ Return number of items in list.
40
+ =end
41
+ def count
42
+ keys.count
43
+ end
44
+
45
+ =begin rdoc
46
+ Return first item list.
47
+ =end
48
+ def first
49
+ keys.first
50
+ end
51
+
52
+ =begin rdoc
53
+ Return last item list.
54
+ =end
55
+ def last
56
+ keys.last
57
+ end
58
+
59
+ =begin rdoc
60
+ Yield each ident in list.
61
+
62
+ See keys.
63
+ =end
64
+ def each
65
+ keys.each { |key| yield key }
66
+ end
67
+
68
+ =begin rdoc
69
+ Return instance of ModelItem class for 'ident'.
70
+ =end
71
+ def [](ident)
72
+ @item_class.new(@model, @item_class.instance_path(@base_path, ident))
73
+ end
74
+
75
+ =begin rdoc
76
+ Add an instance of ModelItem class to 'parent' based on 'args'.
77
+
78
+ Note: This calls ModelItemClass.create, so args must be a suitable Hash. When
79
+ a ProxyModelItemClass is used as the item class, the args will be passed to
80
+ ProxyModelItemClass.create.
81
+ =end
82
+ def add(parent, args)
83
+ @item_class.create(parent, args)
84
+ end
85
+
86
+ =begin rdoc
87
+ Delete instance of ModelItem from list.
88
+
89
+ Note: this has the same effect as just calling item#delete.
90
+ =end
91
+ def delete(ident)
92
+ item = self[ident]
93
+ item.delete if item
94
+ end
95
+ end
96
+
97
+ end
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env ruby
2
+ # :title: Git-DS::ModelItemProxy
3
+ =begin rdoc
4
+
5
+ Copyright 2011 Thoughtgang <http://www.thoughtgang.org>
6
+ =end
7
+
8
+ require 'git-ds/model/item_list'
9
+
10
+ module GitDS
11
+
12
+ =begin rdoc
13
+ Exception raised by errors in creating ModelItemClassProxy instances, e.g.
14
+ bad target or link path.
15
+ =end
16
+ class ProxyItemError < RuntimeError
17
+ end
18
+
19
+ =begin rdoc
20
+ Proxy a ModelItem class.
21
+
22
+ This is used to store a link to a ModelItem class instance. A ModelItemList can
23
+ be passed a ModelItemClassProxy instance as its cls parameter in order to
24
+ store a list of links to ModelItem class instances.
25
+ =end
26
+ class ModelItemClassProxy
27
+
28
+ def initialize(cls)
29
+ @true_class = cls
30
+ end
31
+
32
+ =begin rdoc
33
+ List ModelItem class instances contained in this list.
34
+
35
+ Note: this is passed to the proxied class, as it is just a list of idents.
36
+
37
+ Instantiating and adding an ident is handled by this class.
38
+ =end
39
+ def list_in_path(model, path)
40
+ @true_class.list_in_path(model, path)
41
+ end
42
+
43
+ =begin rdoc
44
+ Return instance of ModelItem class for 'ident'.
45
+ =end
46
+ def new(model, link_path)
47
+ # read path to ModelItem instance from link file at 'link_path'
48
+ instance_path = model.get_item(link_path)
49
+ raise ProxyItemError.new("Invalid ProxyItem path: #{link_path}") if \
50
+ (not instance_path) || (instance_path.empty?)
51
+
52
+ @true_class.new(model, instance_path.chomp)
53
+ end
54
+
55
+ =begin rdoc
56
+ This is passed to the proxied class, as it just returns class_dir + ident.
57
+ =end
58
+ def instance_path(base_path, ident)
59
+ @true_class.instance_path(base_path, ident)
60
+ end
61
+
62
+ =begin rdoc
63
+ Create a link to ModelItem.
64
+
65
+ The ModelItem class ident() method will be used to find the ident of the
66
+ instance in the args Hash.
67
+
68
+ The full path to the instance is expected to be in the :path key of the args
69
+ Hash.
70
+
71
+ If args[:fs] is not nil or false, the link file will be created on-filesystem
72
+ as well as in-db.
73
+ =end
74
+ def create(parent, args)
75
+ link_path = instance_path(parent.path, @true_class.ident(args))
76
+ raise ProxyItemError.new("Invalid ProxyItem path: #{link_path}") if \
77
+ (not link_path) || (link_path.empty?)
78
+
79
+ path = args[:path]
80
+ raise ProxyItemError.new('Invalid ModelItem path') if (not path) || \
81
+ (path.empty?)
82
+
83
+ # write path to ModelItem into link file at 'instance path'
84
+ args[:fs] ? parent.model.add_fs_item(link_path, path.to_s + "\n") \
85
+ : parent.model.add_item(link_path, path.to_s + "\n")
86
+ end
87
+
88
+ end
89
+
90
+ =begin rdoc
91
+ A generic list of links to ModelItemClass instance of a specific class.
92
+
93
+ Example:
94
+ class a/1/name
95
+ class a/1/data
96
+ class a/2/name
97
+ class a/2/data
98
+ class b/1/a/1
99
+ class b/1/a/2
100
+ class b/$ID/a is a list of links to class a objects.
101
+ =end
102
+ class ProxyItemList < ModelItemList
103
+
104
+ def initialize(cls, model, path)
105
+ @true_class = cls
106
+ @proxy_class = ModelItemClassProxy.new(cls)
107
+ super @proxy_class, model, path
108
+ end
109
+
110
+ =begin rdoc
111
+ Add a link to 'obj' to the list.
112
+ =end
113
+ def add(parent, obj, on_fs=false)
114
+ args = { :path => obj.path, :fs => on_fs }
115
+ args[@true_class.ident_key] = obj.ident
116
+ super parent, args
117
+ end
118
+
119
+ =begin rdoc
120
+ Delete a link from the list. This does not delete the object that was linked
121
+ to.
122
+ =end
123
+ def delete(ident)
124
+ @model.delete_item @proxy_class.instance_path(@base_path, ident)
125
+ end
126
+ end
127
+
128
+ end
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env ruby
2
+ # :title: Git-DS::PropertyDefinition
3
+ =begin rdoc
4
+
5
+ Copyright 2011 Thoughtgang <http://www.thoughtgang.org>
6
+ =end
7
+
8
+ require 'git-ds/shared'
9
+
10
+ module GitDS
11
+
12
+ =begin rdoc
13
+ Exception raised when duplicate properties (i.e. having the same name) are
14
+ defined for a single class.
15
+ =end
16
+ class DuplicatePropertyError < ArgumentError
17
+ def initialize(name)
18
+ super "Duplicate property '#{name}'"
19
+ end
20
+ end
21
+
22
+ =begin rdoc
23
+ Exception raised when Property#valid? returns false.
24
+ =end
25
+ class InvalidPropertyValueError < ArgumentError
26
+ def initialize(name, value)
27
+ super "Invalid value '#{value}' for property '#{name}'"
28
+ end
29
+ end
30
+
31
+ =begin rdoc
32
+ A definition of a ModelItem property.
33
+
34
+ These are stored in the class, and are used to wrap access to properties
35
+ by ModelItem objects.
36
+ =end
37
+ class PropertyDefinition
38
+
39
+ =begin rdoc
40
+ Name of the property, e.g. 'size'.
41
+ =end
42
+ attr_accessor :name
43
+
44
+ =begin rdoc
45
+ Default value, e.g. '0'.
46
+ =end
47
+ attr_accessor :default_value
48
+
49
+ =begin rdoc
50
+ Property exists on-disk as well as in the object db.
51
+ =end
52
+ attr_accessor :on_fs
53
+
54
+ =begin rdoc
55
+ Block used to validate data when set.
56
+ =end
57
+ attr_accessor :validation_block
58
+
59
+ def initialize(name, default=nil, fs=false, &block)
60
+ @name = name
61
+ @default_value = default
62
+ @on_fs = fs
63
+ @validation_block = (block_given?) ? block : nil
64
+ end
65
+
66
+ =begin rdoc
67
+ Get full path to BLOB for property based on parent directory.
68
+ =end
69
+ def path(parent_path)
70
+ parent_path + ::File::SEPARATOR + name.to_s
71
+ end
72
+
73
+ =begin rdoc
74
+ Read value from ModelItem at path in Model.
75
+ This just returns the String value of the property file contents; subclasses
76
+ should wrap this with a call that will generate an Integer, List, etc from
77
+ the contents.
78
+ =end
79
+ def get(model, parent_path)
80
+ val = model.get_item(path(parent_path))
81
+ val ? val.chomp : ''
82
+ end
83
+
84
+ =begin rdoc
85
+ Write value to ModelItem at path in Model.
86
+
87
+ Note: this returns the String representation of the value as written to the
88
+ Property BLOB.
89
+ =end
90
+ def set(model, parent_path, value)
91
+ raise InvalidPropertyValueError.new(name, value) if not valid? value
92
+ val = value.to_s
93
+
94
+ if value.kind_of?(Array)
95
+ val = value.join("\n")
96
+ elsif value.kind_of?(Hash)
97
+ val = value.inspect
98
+ end
99
+
100
+ on_fs ? model.add_fs_item(path(parent_path), val + "\n") \
101
+ : model.add_item(path(parent_path), val + "\n")
102
+ val
103
+ end
104
+
105
+ =begin rdoc
106
+ If property has a validation block, invoke it to determine if value is
107
+ valid. Otherwise, return true.
108
+ =end
109
+ def valid?(value)
110
+ blk = self.validation_block
111
+ blk ? blk.call(value) : true
112
+ end
113
+
114
+ alias :default :default_value
115
+
116
+ end
117
+
118
+ =begin rdoc
119
+ A Property that is a link to a ModelItem class instance.
120
+ =end
121
+ class ProxyProperty < PropertyDefinition
122
+ attr_reader :obj_class
123
+ def initialize(name, cls, fs=false, &block)
124
+ super name, nil, fs, &block
125
+ @obj_class = cls
126
+ end
127
+
128
+ =begin rdoc
129
+ Write path of object to property.
130
+ =end
131
+ def set(model, parent_path, obj)
132
+ super model, parent_path, obj.path
133
+ end
134
+
135
+ =begin rdoc
136
+ Instantiate object from path stored in property.
137
+ =end
138
+ def get(model, parent_path)
139
+ path = super model, parent_path
140
+ @obj_class.new(model, path) if path && (not path.empty?)
141
+ end
142
+ end
143
+
144
+ end
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ # :title: Git-DS::RootItem
3
+ =begin rdoc
4
+
5
+ Copyright 2011 Thoughtgang <http://www.thoughtgang.org>
6
+ =end
7
+
8
+ require 'git-ds/shared'
9
+ require 'git-ds/model/item'
10
+
11
+ module GitDS
12
+
13
+ =begin rdoc
14
+ A mock ModelItem that acts as the root of the data model.
15
+ =end
16
+ class RootItem
17
+ attr_reader :model
18
+ attr_reader :path
19
+
20
+ def initialize(model)
21
+ @path = ''
22
+ @model = model
23
+ end
24
+
25
+ def delete
26
+ # nop
27
+ end
28
+
29
+ def self.name
30
+ ''
31
+ end
32
+
33
+ def self.path
34
+ ''
35
+ end
36
+
37
+ def self.create(parent, args)
38
+ return @model.root
39
+ end
40
+
41
+ def self.list
42
+ return []
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,455 @@
1
+ #!/usr/bin/env ruby
2
+ # :title: Git-DS::Repo
3
+ =begin rdoc
4
+ Grit wrappers.
5
+
6
+ Copyright 2010 Thoughtgang <http://www.thoughtgang.org>
7
+ =end
8
+
9
+ require 'rubygems'
10
+ require 'grit'
11
+
12
+ require 'git-ds/index'
13
+ require 'git-ds/shared'
14
+
15
+ # TODO: mutex for staging
16
+ # TODO: cross-process (file) locking? via config or something. Maybe in Database
17
+
18
+ module GitDS
19
+
20
+ =begin rdoc
21
+ Error raised when a command-line Git command files.
22
+ =end
23
+ class CommandError < RuntimeError
24
+ end
25
+
26
+ =begin rdoc
27
+ A Git repository.
28
+
29
+ Note: StagingIndex is cached, as it is from the command line.
30
+ =end
31
+ class Repo < Grit::Repo
32
+ GIT_DIR = ::File::SEPARATOR + '.git'
33
+
34
+ DEFAULT_TAG = '0.0.0'
35
+ attr_reader :last_branch_tag
36
+
37
+ DEFAULT_BRANCH='master'
38
+ attr_reader :current_branch
39
+
40
+ attr_reader :path
41
+
42
+ # TODO: something more intelligent, e.g. with git/repo options
43
+ def self.create(path)
44
+ Grit::Repo.init(path)
45
+ self.new(path) # discard Grit::Repo object and use a Repo object
46
+ end
47
+
48
+ =begin rdoc
49
+ Return the top-level directory of the repo containing the location 'path'.
50
+ =end
51
+ def self.top_level(path='.')
52
+ local = (path == '.')
53
+ old_dir = nil
54
+
55
+ if path != '.'
56
+ old_dir = Dir.getwd
57
+ dest = (File.directory? path) ? path : File.dirname(path)
58
+ Dir.chdir dest
59
+ end
60
+
61
+ dir = `git rev-parse --show-toplevel`
62
+
63
+ Dir.chdir old_dir if old_dir
64
+
65
+ dir.chomp
66
+ end
67
+
68
+ =begin rdoc
69
+ Initialize a repo from the .git subdir in the given path.
70
+ =end
71
+ def initialize(path)
72
+ path.chomp(GIT_DIR) if path.end_with? GIT_DIR
73
+ @path = (path.empty?) ? '.' : path
74
+
75
+ # TODO: get last branch tag from repo
76
+ # prob as a git-config
77
+ @last_branch_tag = DEFAULT_TAG.dup
78
+ @current_branch = DEFAULT_BRANCH.dup
79
+ @staging_index = nil
80
+ @saved_stages = {}
81
+
82
+ super(@path + GIT_DIR)
83
+ end
84
+
85
+ =begin rdoc
86
+ Return the top-level directory of the repo (the parent of .git).
87
+ =end
88
+ def top_level
89
+ git.git_dir.chomp(GIT_DIR)
90
+ end
91
+
92
+ =begin rdoc
93
+ Return true if path exists in repo (on fs or in-tree)
94
+ =end
95
+ def include?(path)
96
+ path_to_object(path) ? true : false
97
+ end
98
+
99
+ alias :exist? :include?
100
+
101
+ # ----------------------------------------------------------------------
102
+ =begin rdoc
103
+ Return a cleaned-up version of the tag name, suitable for use as a filename.
104
+ Replaces all non-alphanumeric characters (except "-.,") with "_".
105
+ =end
106
+ def clean_tag(name)
107
+ name.gsub( /[^-.,_[:alnum:]]/, '_' )
108
+ end
109
+
110
+ =begin rdoc
111
+ Returns the next value for a tag. This is primarily used to auto-generate
112
+ tag names, e.g. 1.0.1, 1.0.2, etc.
113
+ =end
114
+ def next_branch_tag
115
+ @last_branch_tag.succ!
116
+ end
117
+
118
+ =begin rdoc
119
+ Return the Head object for the specified branch
120
+ =end
121
+ def branch(tag=@current_branch)
122
+ get_head(tag)
123
+ end
124
+
125
+ =begin rdoc
126
+ Creates a branch in refs/heads and associates it with the specified commit.
127
+ If sha is nil, the latest commit from 'master' is used.
128
+ The canonical name of the tag is returned.
129
+ =end
130
+ def create_branch( tag=next_branch_tag(), sha=nil )
131
+ if not sha
132
+ sha = commits.first.id
133
+ #sha = branches.first.commit.id if not sha
134
+ end
135
+ name = clean_tag(tag)
136
+ update_ref(name, sha)
137
+ name
138
+ end
139
+
140
+ =begin rdoc
141
+ Sets the current branch to the specified tag. This changes the default
142
+ branch for all repo activity and sets HEAD.
143
+ =end
144
+ def set_branch( tag, actor=nil )
145
+ # allow creating of new branches
146
+ opt = (is_head? tag) ? '' : '-b'
147
+
148
+ # Save staging index for current branch
149
+ @saved_stages[@current_branch] = self.staging if self.staging?
150
+
151
+ exec_git_cmd( "git checkout -q -m #{opt} '#{tag}'", actor )
152
+
153
+ # Synchronize staging index (required before merge)
154
+ unstage
155
+ self.staging.sync
156
+
157
+ # Update current_branch info and restore staging for branch
158
+ self.staging = @saved_stages[tag]
159
+ @current_branch = tag
160
+ end
161
+
162
+ =begin rdoc
163
+ Merge specified branch into master.
164
+ =end
165
+ def merge_branch( tag=@current_branch, actor=nil )
166
+ raise "Invalid branch '#{tag}'" if not (is_head? tag)
167
+
168
+ tag.gsub!(/['\\]/, '')
169
+
170
+ # switch to master branch
171
+ set_branch(DEFAULT_BRANCH, actor)
172
+
173
+ # merge target branch to master branch
174
+
175
+ rv = nil
176
+ begin
177
+ rv = exec_git_cmd("git merge -n --no-ff --no-log --no-squash '#{tag}'",
178
+ actor)
179
+ rescue CommandError => e
180
+ $stderr.puts e.message
181
+ end
182
+ rv
183
+ end
184
+
185
+ =begin rdoc
186
+ Tag (name) an object, e.g. a commit.
187
+ =end
188
+ def tag_object(tag, sha)
189
+ git.fs_write("refs/tags/#{clean_tag(tag)}", sha)
190
+ end
191
+
192
+ # ----------------------------------------------------------------------
193
+
194
+ =begin rdoc
195
+ Return an empty git index for the repo.
196
+ =end
197
+ def index_new
198
+ Index.new(self)
199
+ end
200
+
201
+ =begin rdoc
202
+ Return the staging index for the repo.
203
+ =end
204
+ def staging
205
+ # TODO: mutex
206
+ @staging_index ||= StageIndex.new(self)
207
+ end
208
+
209
+ =begin rdoc
210
+ Set the staging index. This can be used to clear the staging index, or to
211
+ use a specific index as the staging index.
212
+ =end
213
+ def staging=(idx)
214
+ # TODO: mutex
215
+ @staging_index = idx
216
+ end
217
+
218
+ =begin rdoc
219
+ Close staging index.
220
+ This discards all (non-committed) changes in the staging index.
221
+ =end
222
+ def unstage
223
+ self.staging=(nil)
224
+ end
225
+
226
+ =begin rdoc
227
+ Return true if a staging index is active.
228
+ =end
229
+ def staging?
230
+ @staging_index != nil
231
+ end
232
+
233
+ alias :index :staging
234
+ alias :index= :staging=
235
+
236
+ =begin rdoc
237
+ Yield staging index to the provided block, then write the index when the
238
+ block returns.
239
+ This allows the Git staging index to be modified from within Ruby, with all
240
+ changes being visible to the Git command-line tools.
241
+ Returns staging index for chaining purposes.
242
+ =end
243
+ def stage(&block)
244
+ idx = self.staging
245
+ rv = yield idx
246
+ idx.build
247
+ rv
248
+ end
249
+
250
+ =begin rdoc
251
+ Read the Git staging index, then commit it with the provided message and
252
+ author info.
253
+ Returns SHA of commit.
254
+ =end
255
+ def stage_and_commit(msg, actor=nil, &block)
256
+ stage(&block)
257
+ self.staging.commit(msg, actor)
258
+ end
259
+
260
+ # ----------------------------------------------------------------------
261
+ =begin rdoc
262
+ Change to the Repo#top_level dir, yield to block, then pop the dir stack.
263
+ =end
264
+ def exec_in_git_dir(&block)
265
+ curr = Dir.getwd
266
+ Dir.chdir top_level
267
+ result = yield
268
+ Dir.chdir curr
269
+ result
270
+ end
271
+
272
+ =begin rdoc
273
+ Execute the specified command using Repo#exec_in_git_dir.
274
+ =end
275
+ def exec_git_cmd( cmd, actor=nil )
276
+ old_aname = ENV['GIT_AUTHOR_NAME']
277
+ old_aemail = ENV['GIT_AUTHOR_EMAIL']
278
+ old_cname = ENV['GIT_COMMITTER_NAME']
279
+ old_cemail = ENV['GIT_COMMITTER_EMAIL']
280
+ old_pager = ENV['GIT_PAGER']
281
+
282
+ if actor
283
+ ENV['GIT_AUTHOR_NAME'] = actor.name
284
+ ENV['GIT_AUTHOR_EMAIL'] = actor.email
285
+ ENV['GIT_COMMITTER_NAME'] = actor.name
286
+ ENV['GIT_COMMITTER_EMAIL'] = actor.email
287
+ end
288
+ ENV['GIT_PAGER'] = ''
289
+
290
+ # Note: we cannot use Grit#raw_git_call as it requires an index file
291
+ rv = exec_in_git_dir do
292
+ `#{cmd}`
293
+ raise CommandError, rv if $? != 0
294
+ end
295
+
296
+ ENV['GIT_AUTHOR_NAME'] = old_aname
297
+ ENV['GIT_AUTHOR_EMAIL'] = old_aemail
298
+ ENV['GIT_COMMITTER_NAME'] = old_cname
299
+ ENV['GIT_COMMITTER_EMAIL'] = old_cemail
300
+ ENV['GIT_PAGER'] = old_pager
301
+
302
+ rv
303
+ end
304
+
305
+ # ----------------------------------------------------------------------
306
+ alias :add_files :add
307
+
308
+ =begin rdoc
309
+ Add a DB entry at the filesystem path 'path' with contents 'data'. If
310
+ 'on_fs' is true, the file is created in the filesystem as well.
311
+ This uses the staging index.
312
+ =end
313
+ def add(path, data='', on_fs=false)
314
+ self.stage { |idx| idx.add(path, data, on_fs) }
315
+ end
316
+
317
+ =begin rdoc
318
+ Remove an object from the database. This can be a path to a Tree or a Blob.
319
+ =end
320
+ def delete(path)
321
+ self.stage { |idx| idx.delete(path) }
322
+ end
323
+
324
+ =begin rdoc
325
+ Fetch the contents of a DB or FS object from the object database. This uses
326
+ the staging index.
327
+ =end
328
+ def object_data(path)
329
+ blob = path_to_object(path)
330
+ (blob && blob.kind_of?(Grit::Blob)) ? blob.data : nil
331
+ end
332
+
333
+ # ----------------------------------------------------------------------
334
+ =begin rdoc
335
+ The Tree object for the given treeish reference
336
+ +treeish+ is the reference (default 'master')
337
+ +paths+ is an optional Array of directory paths to restrict the tree (default [])
338
+
339
+ Uses staging index if present and provides wrapper for nil treeish.
340
+
341
+ Examples
342
+ repo.tree('master', ['lib/'])
343
+
344
+ Returns Grit::Tree (baked)
345
+ =end
346
+ def tree(treeish=nil, paths = [])
347
+ begin
348
+ if staging? && (not treeish)
349
+ @staging_index.sync
350
+ super(@staging_index.current_tree.id, paths)
351
+ else
352
+ treeish = 'master' if not treeish
353
+ super
354
+ end
355
+ rescue Grit::GitRuby::Repository::NoSuchPath
356
+
357
+ end
358
+ end
359
+
360
+
361
+ =begin rdoc
362
+ Return the SHA1 of 'path' in repo.
363
+ Uses staging index if present.
364
+ =end
365
+ def path_to_sha(path, head=@current_branch)
366
+ # Return the root of the repo if no path is specified
367
+ return root_sha(head) if (not path) || (path.empty?)
368
+
369
+ if staging?
370
+ @staging_index.sync
371
+ head = @staging_index.current_tree.id
372
+ end
373
+
374
+ dir = tree(head, [path])
375
+ (dir && dir.contents.length > 0) ? dir.contents.first.id : nil
376
+ end
377
+
378
+ =begin rdoc
379
+ Return the SHA of the root Tree in the repository.
380
+
381
+ Uses the staging index if it is active.
382
+ =end
383
+ def root_sha(head=@current_branch)
384
+ if staging?
385
+ @staging_index.sync
386
+ return @staging_index.sha
387
+ end
388
+
389
+ (self.commits.count > 0) ? self.commits.first.tree.id : nil
390
+ end
391
+
392
+ =begin rdoc
393
+ Return a Hash of the contents of 'tree'. The key is the filename, the
394
+ value is the Grit object (a Tree or a Blob).
395
+ =end
396
+ def tree_contents(tree)
397
+ return {} if not tree
398
+ tree.contents.inject({}) { |h,item| h[item.name] = item; h }
399
+ end
400
+
401
+ =begin rdoc
402
+ Return a Hash of the contents of 'path'. This is just a wrapper for
403
+ tree_contents.
404
+ =end
405
+ def list(path=nil)
406
+ sha = path_to_sha(path)
407
+ # ensure correct operation even if path doesn't exist in repo
408
+ t = sha ? tree(sha) : tree('master', (path ? [path] : []))
409
+ t ? tree_contents( t ) : {}
410
+ end
411
+
412
+ =begin rdoc
413
+ Return Hash of all Blob child objects at 'path'.
414
+ =end
415
+ def list_blobs(path='')
416
+ list(path).delete_if { |k,v| not v.kind_of?(Grit::Blob) }
417
+ end
418
+
419
+ =begin rdoc
420
+ Return Hash of all Tree child objects at 'path'.
421
+ =end
422
+ def list_trees(path='')
423
+ list(path).delete_if { |k,v| not v.kind_of?(Grit::Tree) }
424
+ end
425
+
426
+ =begin rdoc
427
+ Returns the raw (git cat-file) representation of a tree.
428
+ =end
429
+ def raw_tree(path, recursive=false)
430
+ # Note: to construct recursive tree:
431
+ # Tree.allocate.construct_initialize(repo, treeish, output)
432
+ # from repo.git.ls_tree or raw_tree
433
+ sha = path_to_sha(path)
434
+ sha ? git.ruby_git.get_raw_tree( sha, recursive ) : ''
435
+ end
436
+
437
+ =begin rdoc
438
+ Fetch an object from the repo based on its path.
439
+
440
+ If a staging index exists, its tree will be used; otherwise, the tree
441
+ 'master' will be used.
442
+
443
+ The object returned will be a Grit::Blob, a Grit::Tree, or nil.
444
+ =end
445
+ def path_to_object(path)
446
+ treeish = (@staging_index ? staging.sha : 'master')
447
+ tree = self.tree(treeish, [path])
448
+ return tree.blobs.first if tree && (not tree.blobs.empty?)
449
+ return tree.trees.first if tree && (not tree.trees.empty?)
450
+ nil
451
+ end
452
+
453
+ end
454
+
455
+ end