gash 0.1

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