gash 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1 -0
- data/Manifest +5 -0
- data/README +3 -0
- data/Rakefile +18 -0
- data/gash.gemspec +79 -0
- data/lib/gash.rb +401 -0
- metadata +78 -0
data/CHANGELOG
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
v0.1. First version.
|
data/Manifest
ADDED
data/README
ADDED
data/Rakefile
ADDED
@@ -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
|
data/gash.gemspec
ADDED
@@ -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: []
|
data/lib/gash.rb
ADDED
@@ -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
|
+
|