git_keyvalue 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +63 -0
- data/Rakefile +9 -0
- data/git_keyvalue.gemspec +30 -0
- data/lib/git_keyvalue.rb +261 -0
- data/lib/git_keyvalue/version.rb +3 -0
- metadata +124 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Alexis Gallagher
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# git_keyvalue
|
2
|
+
|
3
|
+
This gem provides a GET/PUT-style wrapper for a remote git repo.
|
4
|
+
|
5
|
+
In effect, it presents the repo as a key/value store, where every key
|
6
|
+
is a path (always interpreted relative to the repo's root) and every
|
7
|
+
key's value is just the contents of the file at that path.
|
8
|
+
|
9
|
+
You can directly get/put the string contents of files. Or you can use
|
10
|
+
getfile/putfile to copy files from/into the repo, which will be more
|
11
|
+
efficient for large binary files like videos or images.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'git_keyvalue'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install git_keyvalue
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
An example:
|
30
|
+
|
31
|
+
$ require 'rubygems'
|
32
|
+
$ require 'bundler/setup
|
33
|
+
$ require 'git_keyvalue'
|
34
|
+
$ # make sure you have network and permissions access to the repo URL
|
35
|
+
$ # which can be any valid git URL (i.e.: http, ssh, git, or file schemes)
|
36
|
+
$ g = GitKeyvalue::KeyValueRepo.new('http://githuben.com/path/to/repo/')
|
37
|
+
$ g.get('metadata/file1.txt')
|
38
|
+
$ => 'I am the contents of metadata/file1.txt!'
|
39
|
+
$ g.put('metadata/file1.txt','new contents of metadata/file1.txt')
|
40
|
+
$ => nil
|
41
|
+
$ # remote repo has now been updated
|
42
|
+
$ g.put('metadata/file2.txt','contents for metadata/file2.txt')
|
43
|
+
$ => nil
|
44
|
+
$ # you just created a new file file2.txt, in metadata/, in the remote repo
|
45
|
+
|
46
|
+
## Contributing
|
47
|
+
|
48
|
+
1. Fork it
|
49
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
50
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
51
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
52
|
+
5. Create new Pull Request
|
53
|
+
|
54
|
+
## Compiling the docs
|
55
|
+
|
56
|
+
Do ``gem install yard`` to ensure you have yard installed.
|
57
|
+
|
58
|
+
Compile the docs from source docstrings with ``rake yard``.
|
59
|
+
|
60
|
+
Do ``yard server --reload`` to start a webserver where you can browse
|
61
|
+
the generated HTML.
|
62
|
+
|
63
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'git_keyvalue/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "git_keyvalue"
|
8
|
+
spec.version = GitKeyvalue::VERSION
|
9
|
+
spec.authors = ["Alexis Gallagher"]
|
10
|
+
spec.email = ["alexis@alexisgallagher.com"]
|
11
|
+
spec.description = %q{Treat a remote git repo as a simple key/value store}
|
12
|
+
spec.summary = %q{Treat a remote git repo as a simple key/value store}
|
13
|
+
spec.homepage = "https://github.com/algal/git_keyvalue"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.has_rdoc = 'yard'
|
22
|
+
spec.extra_rdoc_files = ['README.md']
|
23
|
+
spec.required_ruby_version = '>= 1.9.3'
|
24
|
+
spec.requirements = 'git (known good with v1.7.9.6)'
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
spec.add_development_dependency "yard"
|
29
|
+
spec.add_development_dependency "redcarpet"
|
30
|
+
end
|
data/lib/git_keyvalue.rb
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
require "git_keyvalue/version"
|
2
|
+
|
3
|
+
module GitKeyvalue
|
4
|
+
# known good with ruby 1.9.3, with git 1.7.9.6
|
5
|
+
|
6
|
+
require 'pathname'
|
7
|
+
require 'tmpdir'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
class KeyValueGitError < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Provides a GET/PUT-style interface for a git repo. In effect, it
|
15
|
+
# presents the repo as a key/value store, where the keys are file
|
16
|
+
# paths (relative to the repo's root) and the values are the contents
|
17
|
+
# of those files.
|
18
|
+
#
|
19
|
+
# == Requirements
|
20
|
+
# Known good with ruby 1.9.3 and git 1.7.9.6.
|
21
|
+
#
|
22
|
+
# == Performance & resource usage
|
23
|
+
#
|
24
|
+
# Not performant. Must clone the repo before performing any
|
25
|
+
# operations. Needs whatever disk space is required for a repo clone.
|
26
|
+
# Clears this space when the object is destroyed.
|
27
|
+
#
|
28
|
+
# == Object lifetime
|
29
|
+
#
|
30
|
+
# Stores the local repo in the OS's temporary directory. Therefore,
|
31
|
+
# you should not expect this object to remain valid across automated
|
32
|
+
# housekeeping events that might destroy this directory.
|
33
|
+
#
|
34
|
+
# == Footnote on shallow cloning
|
35
|
+
#
|
36
|
+
# Okay, technically, this does not clone the entire repo. For better
|
37
|
+
# performance it does a "shallow clone" of the repo, which grabs only
|
38
|
+
# the files necessary to represent the HEAD commit. Such a shallow
|
39
|
+
# clone is officially enough to enable GET operations, which read only
|
40
|
+
# those files anyway. However, according to the git-clone docs, the
|
41
|
+
# shallow clone is _not_ officially enough to enable git-push to
|
42
|
+
# update those files on the remote repo. However, this seems like a
|
43
|
+
# bug in the git-clone docs since, in reality, a shallow clone is
|
44
|
+
# enough and should be enough for pushing new commits, since a new
|
45
|
+
# commit only needs to reference its parent commit(s).
|
46
|
+
#
|
47
|
+
# The bottom line: by using shallow cloning for better perf, this
|
48
|
+
# class is relying on undocumented behavior in git-push. This works
|
49
|
+
# fine as of git version 1.7.9.6. I see no reason to expect this to
|
50
|
+
# break in the future, since this undocumented behavior follows
|
51
|
+
# directly from git's data model, which is stable. However, if it does
|
52
|
+
# break, and you want to switch to using the documented git behavior,
|
53
|
+
# then set USE_SHALLOW_CLONING to false.
|
54
|
+
#
|
55
|
+
class KeyValueRepo
|
56
|
+
private
|
57
|
+
# whether to git-clone only the HEAD commit of the remote repo
|
58
|
+
USE_SHALLOW_CLONING = true
|
59
|
+
|
60
|
+
# @return [Proc] proc which removes the temporary local clone of the repo
|
61
|
+
def self.make_finalizer(tmp_dir)
|
62
|
+
proc do
|
63
|
+
puts 'KeyValueRepo: Remove local repo clone in ' + tmp_dir
|
64
|
+
FileUtils.remove_entry_secure(tmp_dir)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Updates the local clone of the repo
|
70
|
+
# @raise [KeyValueGitError] if cannot pull the repo.
|
71
|
+
def update_local_repo
|
72
|
+
Dir.chdir(@path_to_repo) do
|
73
|
+
success = system('git','pull')
|
74
|
+
if not success
|
75
|
+
raise KeyValueGitError, 'Failed to pull updated version of the repo, even though it was cloned successfully. Aborting.'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Checks if path_in_repo points to a file existing in the repo.
|
82
|
+
#
|
83
|
+
# @param [String] path_in_repo
|
84
|
+
# @return [Boolean] whether
|
85
|
+
#
|
86
|
+
# Even if path_in_repo starts with /, it will be interpreted as
|
87
|
+
# relative to the repo's root.
|
88
|
+
def isFileExistingWithinRepo(path_in_repo)
|
89
|
+
abspath = Pathname.new(File.join(@path_to_repo,path_in_repo))
|
90
|
+
# see if the file exists and is a file
|
91
|
+
if abspath.file?
|
92
|
+
# and if it's within the repo
|
93
|
+
abspath.realpath.to_s.start_with?(Pathname.new(@path_to_repo).realpath.to_s)
|
94
|
+
else
|
95
|
+
false
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Strips any initial / chars from +maybe_abspath+
|
101
|
+
#
|
102
|
+
# @param [String] maybe_abspath
|
103
|
+
# @return [String]
|
104
|
+
#
|
105
|
+
def blindly_relativize_path(maybe_abspath)
|
106
|
+
(maybe_abspath.split('').drop_while {|ch| ch=='/'}).join
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Ensure a file exists and execute a GET-like operation, passed as a block.
|
111
|
+
#
|
112
|
+
# @param path_in_repo [String] relative path of a repo file
|
113
|
+
# @yieldparam abspath [String] absolute filesystem path for the block to GET
|
114
|
+
# @yieldreturn [Object,nil] result of GETting the file, or nil if the block returned its value through side-effects
|
115
|
+
# @return [Object,nil] the result returned by the block, or nil if the file does not exist
|
116
|
+
# @raise [KeyValueGitError] if cannot pull from the repo
|
117
|
+
# @raise [Exception] if the block raises an Exception
|
118
|
+
#
|
119
|
+
# Updates the local repo. Verifies the file exists at
|
120
|
+
# path_in_repo. If it does not exist or is outside of the repo,
|
121
|
+
# returns nil. Otherwise, returns the result of calling the block.
|
122
|
+
#
|
123
|
+
# This method will raise whatever the block raises
|
124
|
+
def outer_get(path_in_repo)
|
125
|
+
update_local_repo
|
126
|
+
if not isFileExistingWithinRepo(path_in_repo)
|
127
|
+
nil
|
128
|
+
else
|
129
|
+
abspath = Pathname.new(File.join(@path_to_repo,path_in_repo)).realpath.to_s
|
130
|
+
yield abspath
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Prepares and executes a PUT-like operation, passed as a block
|
136
|
+
#
|
137
|
+
# @param path_in_repo [String] relative path of repo file
|
138
|
+
# @yield
|
139
|
+
# @return the result returned by the block
|
140
|
+
#
|
141
|
+
# @raise [KeyValueGitError] if can't pull or push the repo
|
142
|
+
# @raise [Exception] if the block raises an Exception
|
143
|
+
#
|
144
|
+
# Update the local repo, changes to its root directory, then calls
|
145
|
+
# the block to execute the PUT operation on the repo's working
|
146
|
+
# tree. Then commits and push that change to the remote repo.
|
147
|
+
def outer_put(path_in_repo)
|
148
|
+
update_local_repo
|
149
|
+
Dir.chdir(@path_to_repo) do
|
150
|
+
|
151
|
+
yield
|
152
|
+
|
153
|
+
# add and commit to repo
|
154
|
+
system('git','add',path_in_repo)
|
155
|
+
system('git','commit','-m','\'git-keyvalue: updating ' + path_in_repo + '\'')
|
156
|
+
success = system('git','push')
|
157
|
+
if not success
|
158
|
+
# restore local repo to a good state
|
159
|
+
system('git','clean','--force','-d')
|
160
|
+
# report the failure
|
161
|
+
raise KeyValueGitError, 'Failed to push commit with updated file. This could be because someone else pushed to the repository in the middle of this operation. If this is the problem, you should be able simply to re-try this operation. If the problem is deeper, you might create a fresh object before re-trying.'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
public
|
167
|
+
|
168
|
+
# @return [String] URL of the remote git repo
|
169
|
+
attr_reader :repo_url
|
170
|
+
# @return [String] absolute filesystem path of the local repo
|
171
|
+
attr_reader :path_to_repo
|
172
|
+
|
173
|
+
##
|
174
|
+
# Clones the remote repo, failing if it is invalid or inaccessible.
|
175
|
+
#
|
176
|
+
# @param repo_url [String] URL of a valid, network-accessible, permissions-accessible git repo
|
177
|
+
#
|
178
|
+
# As it clones the entire repo, this may take a long time if you are
|
179
|
+
# manipulating a large remote repo. Keeps the repo in the OS's
|
180
|
+
# temporary directory, so you should not expect this object to
|
181
|
+
# remain valid across automated cleanups of that temporary directory
|
182
|
+
# (which happen, for instance, typically at restart).
|
183
|
+
#
|
184
|
+
# @raise [KeyValueGitError] if unable to clone the repo
|
185
|
+
def initialize(repo_url)
|
186
|
+
@repo_url = repo_url
|
187
|
+
@path_to_repo = Dir.mktmpdir('KeyValueGitTempDir')
|
188
|
+
if USE_SHALLOW_CLONING
|
189
|
+
# experimental variant. uses undocumented behaviour of
|
190
|
+
# git-clone. This is because setting --depth 1 produces a
|
191
|
+
# shallow clone, which according to the docs does not let you
|
192
|
+
# git-push aftewards, but in reality should and does let you
|
193
|
+
# git-push. This is a bug in the git documentation.
|
194
|
+
success = system('git','clone','--depth','1',@repo_url,@path_to_repo)
|
195
|
+
else
|
196
|
+
# stable variant. uses documented behavior of git-clone
|
197
|
+
success = system('git','clone',@repo_url,@path_to_repo)
|
198
|
+
end
|
199
|
+
if not success
|
200
|
+
raise KeyValueGitError, 'Failed to initialize, because could not clone the remote repo: ' + repo_url + '. Please verify this is a valid git URL, and that any required network connection or login credentials are available.'
|
201
|
+
end
|
202
|
+
ObjectSpace.define_finalizer(self, self.class.make_finalizer(@path_to_repo))
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# Get contents of a file, or nil if it does not exist
|
207
|
+
#
|
208
|
+
# @param path_in_repo [String] relative path of repo file to get
|
209
|
+
# @return [String,nil] string contents of file, or nil if non-existent
|
210
|
+
#
|
211
|
+
def get(path_in_repo)
|
212
|
+
outer_get(path_in_repo) { |abspath| File.read(abspath) }
|
213
|
+
end
|
214
|
+
|
215
|
+
##
|
216
|
+
# Copies the repo file at +path_in_repo+ to +dest_path+
|
217
|
+
#
|
218
|
+
# @param path_in_repo [String] relative path of repo file to get
|
219
|
+
# @param dest_path [String] path to which to copy the gotten file
|
220
|
+
#
|
221
|
+
# Does no validation regarding dest_path. If dest_path points to a
|
222
|
+
# file, it will overwrite that file. If it points to a directory, it
|
223
|
+
# will copy into that directory.
|
224
|
+
def getfile(path_in_repo, dest_path)
|
225
|
+
outer_get(path_in_repo) { |abspath| FileUtils.cp(abspath, dest_path) }
|
226
|
+
end
|
227
|
+
|
228
|
+
##
|
229
|
+
# Sets the contents of the file at +path_in_repo+, creating it if necessary
|
230
|
+
#
|
231
|
+
# @param path_in_repo [String] relative path of repo file to add or update
|
232
|
+
# @param string_value [String] the new contents for the file at this path
|
233
|
+
#
|
234
|
+
def put(path_in_repo, string_value)
|
235
|
+
path_in_repo = blindly_relativize_path(path_in_repo)
|
236
|
+
outer_put(path_in_repo) {
|
237
|
+
# create parent directories if needed
|
238
|
+
FileUtils.mkdir_p(File.dirname(path_in_repo))
|
239
|
+
# write new file contents
|
240
|
+
File.open(path_in_repo,'w') { |f| f.write(string_value) }
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# Sets the contents of the file at path, creating it if necessary
|
246
|
+
#
|
247
|
+
# @param path_in_repo [String] relative path of repo file to add or update
|
248
|
+
# @param src_file_path [String] file to use for replacing +path_in_repo+
|
249
|
+
#
|
250
|
+
def putfile(path_in_repo, src_file_path)
|
251
|
+
path_in_repo = blindly_relativize_path(path_in_repo)
|
252
|
+
outer_put(path_in_repo) {
|
253
|
+
# create parent directories if needed
|
254
|
+
FileUtils.mkdir_p(File.dirname(path_in_repo))
|
255
|
+
# copy file at src_file_path into the path_in_repo
|
256
|
+
abspath = Pathname.new(File.join(@path_to_repo,path_in_repo)).to_s
|
257
|
+
FileUtils.cp(src_file_path, abspath)
|
258
|
+
}
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git_keyvalue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alexis Gallagher
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: yard
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: redcarpet
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Treat a remote git repo as a simple key/value store
|
79
|
+
email:
|
80
|
+
- alexis@alexisgallagher.com
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files:
|
84
|
+
- README.md
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- LICENSE.txt
|
89
|
+
- README.md
|
90
|
+
- Rakefile
|
91
|
+
- git_keyvalue.gemspec
|
92
|
+
- lib/git_keyvalue.rb
|
93
|
+
- lib/git_keyvalue/version.rb
|
94
|
+
homepage: https://github.com/algal/git_keyvalue
|
95
|
+
licenses:
|
96
|
+
- MIT
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 1.9.3
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
hash: -728910074357862692
|
116
|
+
requirements:
|
117
|
+
- git (known good with v1.7.9.6)
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 1.8.25
|
120
|
+
signing_key:
|
121
|
+
specification_version: 3
|
122
|
+
summary: Treat a remote git repo as a simple key/value store
|
123
|
+
test_files: []
|
124
|
+
has_rdoc: yard
|