Skip to content

Instantly share code, notes, and snippets.

@koraktor
Created August 31, 2011 10:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koraktor/642b33621142a321264b to your computer and use it in GitHub Desktop.
Save koraktor/642b33621142a321264b to your computer and use it in GitHub Desktop.
CodeBrawl contest "Key/value stores"

Geist

Geist is a Git-backed key-value store written in Ruby. You may call it "Git Explicit Item Storage Tool" if you really want too.

Usage

require 'geist'

g = Geist.new '~/some/storage/path'
g.set :foo, 'bar'
g.set :baz, 123
g.set :name => 'geist', :platform => 'ruby'
g.delete :baz

g.keys                 #=> ["foo", "name", "platform"]
g.get :foo             #=> "bar"
g.get :baz             #=> nil
g.get :name, :platform #=> ["geist", "ruby"]

Internals

To be honest, the introduction was an outright fabrication. Geist is just a Ruby API to misuse Git as a simple key-value store. Git itself is a pretty good key-value store used to preserve blob (file), tree (directory), commit and tag objects.

The Ruby objects to store as values will be marshalled into Git blob objects. These objects are referenced with lightweight Git tags named by the given key.

Git will not double store duplicate values. Instead, the different key tags will refer the same Git object.

Caveats

As Geist uses Git tags as keys, only objects with a working #to_s method can be used as keys. Additionally, based on Git's ref naming rules, Geist rejects keys that can't be used as Git tag names, e.g. containing non-printable characters or backslashes.

License

This code is free software; you can redistribute it and/or modify it under the terms of the new BSD License. A copy of this license can be found in the LICENSE file.

Credits

  • Sebastian Staudt – koraktor(at)gmail.com
require 'fileutils'
require 'open4'
class Geist
def initialize(path)
@path = File.expand_path path
if !File.exist? @path
FileUtils.mkdir_p @path
cmd 'init'
elsif !cmd('ls-files').success?
raise "#{@path} is not a Git repository."
end
end
def delete(*keys)
success = true
keys.each do |key|
if id_for(key).nil?
success = false
else
cmd "tag -d '#{key}'"
end
end
success
end
def get(*keys)
return nil if keys.empty?
values = []
keys.each do |key|
value = nil
status = cmd "show '#{key}'" do |stdin, stdout|
if select [stdout]
blob = stdout.read.strip
value = Marshal.load blob unless blob.empty?
end
end
values << (status.success? ? value : nil)
end
keys.size == 1 ? values.first : values
end
def keys
keys = nil
cmd 'tag -l' do |stdin, stdout|
keys = stdout.lines.to_a.map(&:strip) if select [stdout]
end
keys
end
def set(keys, value = nil)
keys = { keys => value } unless keys.is_a? Hash
keys.each do |key, val|
if key.to_s.match /(?:[\W\s^~:?*\[\\]|\.\.|@\{|(?:\/|\.|\.lock)$|^$)/
warn "Warning: Invalid key '#{key}'"
return
end
delete key unless id_for(key).nil?
id = nil
cmd 'hash-object --stdin -w' do |stdin, stdout|
stdin.write Marshal.dump(val)
stdin.close
id = stdout.read.strip if select [stdout]
end
cmd "tag -f '#{key}' #{id}"
end
end
private
def cmd(git_cmd, &block)
cmd = "git --git-dir #{@path} #{git_cmd}"
status = Open4::popen4 cmd do |pid, stdin, stdout, stderr|
block.call stdin, stdout if block_given?
stdin.close unless stdin.closed?
stdout.close
stderr.close
end
status
end
def id_for(key)
id = nil
status = cmd "rev-parse '#{key}'" do |stdin, stdout|
id = stdout.read.strip if select [stdout]
end
status.success? ? id : nil
end
end
@Keoven
Copy link

Keoven commented Sep 6, 2011

I love it when someone tries to use something in a way it's not necessarily meant to be used... Nice Idea...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment