gash 0.1

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 (7) hide show
  1. data/CHANGELOG +1 -0
  2. data/Manifest +5 -0
  3. data/README +3 -0
  4. data/Rakefile +18 -0
  5. data/gash.gemspec +79 -0
  6. data/lib/gash.rb +401 -0
  7. metadata +78 -0
@@ -0,0 +1 @@
1
+ v0.1. First version.
@@ -0,0 +1,5 @@
1
+ CHANGELOG
2
+ lib/gash.rb
3
+ Rakefile
4
+ README
5
+ Manifest
data/README ADDED
@@ -0,0 +1,3 @@
1
+ = Gash, Git + Hash
2
+
3
+ Please see http://dojo.rubyforge.org/gash or the documentation in lib/gash.rb!
@@ -0,0 +1,18 @@
1
+ require 'echoe'
2
+ require 'hanna/rdoctask'
3
+
4
+ Echoe.new('gash') do |p|
5
+ p.project = "dojo"
6
+ p.author = "Magnus Holm"
7
+ p.email = "judofyr@gmail.com"
8
+ p.summary = "Git + Hash"
9
+ p.url = "http://dojo.rubyforge.org/gash/"
10
+ p.rdoc_options += ["--main", "Gash", "--title", "Gash"]
11
+ end
12
+
13
+ Rake::Task[:publish_docs].instance_eval do
14
+ @actions.clear
15
+ enhance do
16
+ sh("rsync -avc --delete doc/* judofyr@rubyforge.org:/var/www/gforge-projects/dojo/gash/")
17
+ end
18
+ end
@@ -0,0 +1,79 @@
1
+
2
+ # Gem::Specification for Gash-0.1
3
+ # Originally generated by Echoe
4
+
5
+ --- !ruby/object:Gem::Specification
6
+ name: gash
7
+ version: !ruby/object:Gem::Version
8
+ version: "0.1"
9
+ platform: ruby
10
+ authors:
11
+ - Magnus Holm
12
+ autorequire:
13
+ bindir: bin
14
+
15
+ date: 2008-09-04 00:00:00 +02:00
16
+ default_executable:
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: echoe
20
+ type: :development
21
+ version_requirement:
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: "0"
27
+ version:
28
+ description: Git + Hash
29
+ email: judofyr@gmail.com
30
+ executables: []
31
+
32
+ extensions: []
33
+
34
+ extra_rdoc_files:
35
+ - CHANGELOG
36
+ - lib/gash.rb
37
+ - README
38
+ files:
39
+ - CHANGELOG
40
+ - lib/gash.rb
41
+ - Rakefile
42
+ - README
43
+ - Manifest
44
+ - gash.gemspec
45
+ has_rdoc: true
46
+ homepage: http://dojo.rubyforge.org/gash/
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --line-numbers
50
+ - --inline-source
51
+ - --title
52
+ - Gash
53
+ - --main
54
+ - README
55
+ - --main
56
+ - Gash
57
+ - --title
58
+ - Gash
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "="
70
+ - !ruby/object:Gem::Version
71
+ version: "1.2"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project: dojo
76
+ rubygems_version: 1.2.0
77
+ specification_version: 2
78
+ summary: Git + Hash
79
+ test_files: []
@@ -0,0 +1,401 @@
1
+ require 'delegate'
2
+
3
+ # == What is Gash?
4
+ #
5
+ # * Gash lets you access a Git-repo as a Hash.
6
+ # * Gash only cares about the data, not the commits.
7
+ # * Gash only cares about the _latest_ data.
8
+ # * Gash can commit.
9
+ # * Gash doesn't touch your working directory
10
+ # * Gash will automatically create branches if they don't exists.
11
+ # * Gash only loads what it needs, so it handles large repos well.
12
+ #
13
+ # == How do you use it?
14
+ #
15
+ # gash = Gash.new
16
+ # gash["README"] = "new content"
17
+ # gash.commit("Some changes...")
18
+ #
19
+ # It's also important to remember that a Gash is simply a Tree, so you can
20
+ # also call those methods.
21
+ #
22
+ # <strong>See also</strong>: #new, #commit, Tree
23
+ #
24
+ # == Limitation
25
+ #
26
+ # The only Hash-like features which currently works is #[] and #[]=,
27
+ # so don't try #merge or something like that.
28
+ class Gash < SimpleDelegator
29
+ module Errors
30
+ # This error is raised when the Git-command fails.
31
+ class Git < StandardError; end
32
+ end
33
+
34
+ # Some common methods used by both Tree and Blob.
35
+ module Helpers
36
+ attr_accessor :sha1, :mode, :parent
37
+
38
+ # Sets the accessors using a Hash:
39
+ #
40
+ # tree = Gash::Tree.new(:sha1 => "some thing", :mode => "some thing",
41
+ # :parent => "some parent")
42
+ # tree.sha1 == "some thing"
43
+ # tree.mode == "some thing"
44
+ # tree.parent == "some parent"
45
+ def initialize(opts = {})
46
+ opts.each do |key, value|
47
+ send("#{key}=", value)
48
+ end
49
+ end
50
+
51
+ # Checks if this is a Blob.
52
+ def blob?; self.class == Gash::Blob end
53
+ # Checks if this is a Tree.
54
+ def tree?; self.class == Gash::Tree end
55
+ # Checks if this object has been changed (since last commit).
56
+ def changed?; !@sha1 end
57
+ # Mark this, and all parents as changed.
58
+ def changed!; @sha1 = nil;parent.changed! end
59
+ # Returns the Gash-object (top-parent).
60
+ def gash; parent.gash end
61
+ end
62
+
63
+ # A Tree is a Hash which can store other instances of Tree and Blob.
64
+ #
65
+ # <strong>See also</strong>: Helpers, Blob
66
+ class Tree < Hash
67
+ include Helpers
68
+
69
+ # Retrieves the _value_ stored as +key+:
70
+ #
71
+ # tree["FILE"] == the file
72
+ # tree["DIR/FILE"] == tree["DIR"]["FILE"] = another file
73
+ #
74
+ # It will automatically call the +load!+-method in order to load
75
+ # it from the repo. Set +lazy+ to +true+ if this is not what you want:
76
+ #
77
+ # blob = tree["FILE", true] == #<Blob:1234...>
78
+ # # do some other stuff...
79
+ # blob.laod! # Load it now!
80
+ def [](key, lazy = nil)
81
+ ret = if key.include?("/")
82
+ key, rest = key.split("/", 2)
83
+ value = super(key)
84
+ return if value.nil?
85
+ value[rest]
86
+ else
87
+ super(key)
88
+ end
89
+ ensure
90
+ ret.load! if ret.respond_to?(:load!) && !lazy
91
+ end
92
+ alias / []
93
+
94
+ # Stores the given _value_:
95
+ #
96
+ # tree["FILE"] = "Content"
97
+ #
98
+ # Unless it's already a Tree or a Blob, it will be converted to a Blob,
99
+ # and the parent will _always_ be set to +self+.
100
+ #
101
+ # tree["FILE"] = "Content"
102
+ # # is the same as:
103
+ # tree["FILE"] = Gash::Blob.new(:content => "Content", :parent => tree)
104
+ #
105
+ # It will also mark the object as changed (using <code>Helpers#changed!</code>).
106
+ # Set +not_changed+ to +true+ if this is not what you want.
107
+ #
108
+ # (If you give it three arguments, then the second one will act as
109
+ # +not_changed+, not the third):
110
+ #
111
+ # tree["FILE", true] = "Test"
112
+ # tree["FILE"].changed? # => false
113
+ def []=(key, value, not_changed = nil)
114
+ key, value, not_changed = if not_changed.nil?
115
+ [key, value]
116
+ else
117
+ [key, not_changed, value]
118
+ end
119
+
120
+ if key.include?("/")
121
+ keys = key.split("/")
122
+ name = keys.pop
123
+ keys.inject(self) do |memo, i|
124
+ memo[i] = Tree.new(:parent => self) unless memo.include?(i)
125
+ memo[i, true]
126
+ end[name, not_changed] = value
127
+ else
128
+ value = case value
129
+ when Tree, Blob
130
+ value
131
+ else
132
+ Blob.new(:content => value.to_s)
133
+ end
134
+ value.parent = self
135
+ super(key, value)
136
+ end
137
+ ensure
138
+ self.changed! unless not_changed
139
+ end
140
+ end
141
+
142
+ # A Blob represent a string:
143
+ #
144
+ # blob = Gash::Blob.new(:content => "Some content")
145
+ # blob # => "Some content"
146
+ #
147
+ # == Using SHA1
148
+ #
149
+ # However, if you provide a SHA1 (and have a parent which is connected to
150
+ # a Gash-object) it will then load the content from the repo when needed:
151
+ #
152
+ # blob = Gash::Blob.new(:sha1 => "1234" * 10, :parent => gash_OR_tree_connected_to_gash)
153
+ # blob # => #<Blob:1234123412341234123412341234123412341234>
154
+ # blob.upcase # It's loaded when needed
155
+ # #blob.load! # or forced with #load!
156
+ # blob # => "Content of the blob"
157
+ #
158
+ # Tree#[]= automatically sets the parent to itself, so you don't need to
159
+ # provide it then:
160
+ #
161
+ # tree["FILE"] = Gash::Blob.new(:sha1 => a_sha1)
162
+ #
163
+ # <strong>See also</strong>: Helpers, Tree
164
+ class Blob < Delegator
165
+ include Helpers
166
+ attr_accessor :content
167
+
168
+ # Loads the file from Git, unless it's already been loaded.
169
+ def load!
170
+ @content ||= gash.send(:cat_file, @sha1)
171
+ end
172
+
173
+ def inspect #:nodoc:
174
+ @content ? @content.inspect : (@sha1 ? "#<Blob:#{@sha1}>" : to_s.inspect)
175
+ end
176
+
177
+ def __getobj__ #:nodoc:
178
+ @content ||= @sha1 ? load! : ''
179
+ end
180
+ alias_method :to_s, :__getobj__
181
+ end
182
+
183
+ attr_accessor :branch, :repository
184
+
185
+ # Opens the +repo+ with the specified +branch+.
186
+ #
187
+ # <strong>Please note:</strong> The +repo+ must link to the actual repo,
188
+ # not the working directory!
189
+ def initialize(branch = "master", repo = ".git")
190
+ @branch = branch
191
+ @repository = File.expand_path(repo)
192
+ __setobj__(Tree.new(:parent => self))
193
+ update!
194
+ end
195
+
196
+ def gash #:nodoc:
197
+ self
198
+ end
199
+
200
+ def changed! #:nodoc:
201
+ @sha1 = nil
202
+ end
203
+
204
+ # Fetch the latest data from Git; you can use this as a +clear+-method.
205
+ def update!
206
+ clear
207
+ self.sha1 = git_tree_sha1
208
+ git_tree do |line|
209
+ line.strip!
210
+ mode = line[0, 6]
211
+ type = line[7]
212
+ sha1 = line[12, 40]
213
+ name = line[53..-1]
214
+ if name[0] == ?" && name[-1] == ?"
215
+ name = eval(name)
216
+ end
217
+ name = name[/[^\/]+$/]
218
+ parent = if $`.empty?
219
+ self
220
+ else
221
+ self[$`.chomp("/")]
222
+ end
223
+ parent[name, true] = case type
224
+ when ?b
225
+ Blob.new(:sha1 => sha1, :mode => mode)
226
+ when ?t
227
+ Tree.new(:sha1 => sha1, :mode => mode)
228
+ end
229
+ end
230
+ self
231
+ end
232
+
233
+ # Commit the current changes and returns the commit-hash.
234
+ #
235
+ # Returns +nil+ if nothing has changed.
236
+ def commit(msg)
237
+ return unless changed?
238
+ commit = commit_tree(to_tree!, msg)
239
+ @sha1 = git_tree_sha1
240
+ commit
241
+ end
242
+
243
+ # Checks if the current branch exists
244
+ def branch_exists?
245
+ git('rev-parse', @branch, '2>&1')
246
+ true
247
+ rescue Errors::Git
248
+ false
249
+ end
250
+
251
+ def inspect #:nodoc:
252
+ __getobj__.inspect
253
+ end
254
+
255
+ private
256
+
257
+ def cat_file(blob)
258
+ git('cat-file', 'blob', blob)
259
+ end
260
+
261
+ def to_tree!(from = self)
262
+ input = []
263
+ from.each do |key, value|
264
+ key = key.inspect
265
+ if value.tree?
266
+ value.sha1 ||= to_tree!(value)
267
+ value.mode ||= "040000"
268
+ input << "#{value.mode} tree #{value.sha1}\t#{key}\n"
269
+ else
270
+ value.sha1 ||= git('hash-object', '-w', '--stdin', :input => value.to_s)
271
+ value.mode ||= "100644"
272
+ input << "#{value.mode} blob #{value.sha1}\t#{key}\n"
273
+ end
274
+ end
275
+ git('mktree', :input => input)
276
+ end
277
+
278
+ def update_head(new_head)
279
+ git('update-ref', 'refs/heads/%s' % @branch, new_head)
280
+ end
281
+
282
+ def commit_tree(tree, msg)
283
+ if branch_exists?
284
+ commit = git('commit-tree', tree, '-p', @branch, :input => msg)
285
+ update_head(commit)
286
+ else
287
+ commit = git('commit-tree', tree, :input => msg)
288
+ git('branch', @branch, commit)
289
+ end
290
+ commit
291
+ end
292
+
293
+ def git_tree(&blk)
294
+ git('ls-tree', '-r', '-t', @branch, '2>&1') do |f|
295
+ f.each_line(&blk)
296
+ end
297
+ rescue Errors::Git
298
+ ""
299
+ end
300
+
301
+ def git_tree_sha1(from = @branch)
302
+ git('rev-parse', @branch + '^{tree}', '2>&1')
303
+ rescue Errors::Git
304
+ end
305
+
306
+ def method_missing(meth, *args, &blk)
307
+ target = self.__getobj__
308
+ unless target.respond_to?(meth)
309
+ Object.instance_method(:method_missing).bind(self).call(meth, *args, &blk)
310
+ end
311
+ target.__send__(meth, *args, &blk)
312
+ end
313
+
314
+ # passes the command over to git
315
+ #
316
+ # ==== Parameters
317
+ # cmd<String>:: the git command to execute
318
+ # *rest:: any number of String arguments to the command, followed by an options hash
319
+ # &block:: if you supply a block, you can communicate with git throught a pipe. NEVER even think about closing the stream!
320
+ #
321
+ # ==== Options
322
+ # :strip<Boolean>:: true to strip the output String#strip, false not to to it
323
+ #
324
+ # ==== Raises
325
+ # Errors::Git:: if git returns non-null, an Exception is raised
326
+ #
327
+ # ==== Returns
328
+ # String:: if you didn't supply a block, the things git said on STDOUT, otherwise noting
329
+ def git(cmd, *rest, &block)
330
+ result, status = run_git(cmd, *rest, &block)
331
+
332
+ if status != 0
333
+ raise Errors::Git.new("Error: #{cmd} returned #{status}. Result: #{result}")
334
+ end
335
+ result
336
+ end
337
+
338
+
339
+ # passes the command over to git and returns its status ($?)
340
+ #
341
+ # ==== Parameters
342
+ # cmd<String>:: the git command to execute
343
+ # *rest:: any number of String arguments to the command, followed by an options hash
344
+ # &block:: if you supply a block, you can communicate with git throught a pipe. NEVER even think about closing the stream!
345
+ #
346
+ # ==== Returns
347
+ # Integer:: the return status of git
348
+ def git_status(cmd, *rest, &block)
349
+ run_git(cmd, *rest, &block)[1]
350
+ end
351
+
352
+ # passes the command over to git (you should not call this directly)
353
+ #
354
+ # ==== Parameters
355
+ # cmd<String>:: the git command to execute
356
+ # *rest:: any number of String arguments to the command, followed by an options hash
357
+ # &block:: if you supply a block, you can communicate with git throught a pipe. NEVER even think about closing the stream!
358
+ #
359
+ # ==== Options
360
+ # :strip<Boolean>:: true to strip the output String#strip, false not to to it
361
+ #
362
+ # ==== Raises
363
+ # Errors::Git:: if git returns non-null, an Exception is raised
364
+ #
365
+ # ==== Returns
366
+ # Array[String, Integer]:: the first item is the STDOUT of git, the second is the return-status
367
+ def run_git(cmd, *args, &block)
368
+ options = if args.last.kind_of?(Hash)
369
+ args.pop
370
+ else
371
+ {}
372
+ end
373
+
374
+ options[:strip] = true unless options.key?(:strip)
375
+
376
+ ENV["GIT_DIR"] = @repository
377
+ cmd = "git #{cmd} #{args.join(' ')}"
378
+
379
+ result = ""
380
+ IO.popen(cmd, "w+") do |f|
381
+ if input = options.delete(:input)
382
+ f.write(input)
383
+ f.close_write
384
+ elsif block_given?
385
+ yield f
386
+ f.close_write
387
+ end
388
+
389
+ result = ""
390
+
391
+ while !f.eof
392
+ result << f.read
393
+ end
394
+ end
395
+ status = $?
396
+
397
+ result.strip! if options[:strip] == true
398
+
399
+ [result, status]
400
+ end
401
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gash
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Magnus Holm
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-09-04 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: echoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: Git + Hash
26
+ email: judofyr@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - CHANGELOG
33
+ - lib/gash.rb
34
+ - README
35
+ files:
36
+ - CHANGELOG
37
+ - lib/gash.rb
38
+ - Rakefile
39
+ - README
40
+ - Manifest
41
+ - gash.gemspec
42
+ has_rdoc: true
43
+ homepage: http://dojo.rubyforge.org/gash/
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --line-numbers
47
+ - --inline-source
48
+ - --title
49
+ - Gash
50
+ - --main
51
+ - README
52
+ - --main
53
+ - Gash
54
+ - --title
55
+ - Gash
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "="
67
+ - !ruby/object:Gem::Version
68
+ version: "1.2"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project: dojo
73
+ rubygems_version: 1.2.0
74
+ signing_key:
75
+ specification_version: 2
76
+ summary: Git + Hash
77
+ test_files: []
78
+