Skip to content

Commit

Permalink
better concurrency. better scaling. 20000 entries in 1000 folders sti…
Browse files Browse the repository at this point in the history
…ll performant.
  • Loading branch information
georgi committed Jan 4, 2009
1 parent e5ba191 commit 9dcbdf9
Show file tree
Hide file tree
Showing 7 changed files with 477 additions and 222 deletions.
6 changes: 5 additions & 1 deletion git_store.gemspec
Expand Up @@ -16,7 +16,11 @@ LICENSE
README.md
git_store.gemspec
lib/git_store.rb
spec/git_store_spec.rb
lib/git_store/blob.rb
lib/git_store/tree.rb
lib/git_store/handlers.rb
test/git_store_spec.rb
test/benchmark.rb
}
end

305 changes: 111 additions & 194 deletions lib/git_store.rb
@@ -1,240 +1,157 @@
require 'rubygems'
require 'grit'
require 'yaml'

# This fix ensures sorted yaml maps.
class Hash
def to_yaml( opts = {} )
YAML::quick_emit( object_id, opts ) do |out|
out.map( taguri, to_yaml_style ) do |map|
sort_by { |k, v| k.to_s }.each do |k, v|
map.add( k, v )
end
end
end
end
end
require 'git_store/blob'
require 'git_store/tree'
require 'git_store/handlers'

class GitStore
include Enumerable

class DefaultHandler
def read(name, data)
data
end
attr_reader :repo, :index, :root, :last_commit

def write(data)
data
end
def initialize(path = '.')
@repo = Grit::Repo.new(path)
@root = Tree.new('')
load_last_commit
end

class YAMLHandler
def read(name, data)
YAML.load(data)
end

def write(data)
data.to_yaml
end
def load_last_commit
@last_commit = @repo.commits('master', 1)[0]
end

class RubyHandler
def read(name, data)
Object.module_eval(data)
end

def commit(message="")
head = repo.heads.first
commit_index(message, head ? head.commit.id : nil)
end

class ERBHandler
def read(name, data)
ERB.new(data)
end

def [](*args)
root[*args]
end

Handler = {
'yml' => YAMLHandler.new,
'rhtml' => ERBHandler.new,
'rxml' => ERBHandler.new,
'rb' => RubyHandler.new
}
def []=(*args)
value = args.pop
root[*args] = value
end

Handler.default = DefaultHandler.new
def delete(path)
root.delete(path)
end

class Blob
def load
root.load(repo.tree)
end

attr_reader :id
attr_accessor :name
def each(&block)
root.each(&block)
end

def initialize(*args)
if args.first.is_a?(Grit::Blob)
@blob = args.first
@name = @blob.name
else
@name = args[0]
self.data = args[1]
end
end
def changed?
commit = repo.commits('master', 1)[0]
commit and (last_commit.nil? or last_commit.id != commit.id)
end

def extname
File.extname(name)[1..-1]
end
def refresh!
load if changed?
end

def load(data)
@data = handler.read(name, data)
end
def start_transaction(head = 'master')
@lock = open("#{repo.path}/refs/heads/#{head}.lock", "w")
@lock.flock(File::LOCK_EX)
end

def handler
Handler[extname]
end
def commit_index(message, parents = nil, actor = nil, head = 'master')
start_transaction(head) unless @lock

tree_sha = write_tree(root)

contents = []
contents << ['tree', tree_sha].join(' ')

def data
@data or (@blob and load(@blob.data))
if parents
parents.each do |p|
contents << ['parent', p].join(' ') if p
end
end

def data=(data)
@data = data
if actor
name = actor.name
email = actor.email
else
config = Grit::Config.new(self.repo)
name = config['user.name']
email = config['user.email']
end

def to_s
if handler.respond_to?(:write)
handler.write(data)
else
@blob.data
end

author_string = "#{name} <#{email}> #{Time.now.to_i} -0700"
contents << ['author', author_string].join(' ')
contents << ['committer', author_string].join(' ')
contents << ''
contents << message

commit_sha = put_raw_object(contents.join("\n"), 'commit')

open("#{repo.path}/refs/heads/#{head}", "w") do |file|
file.write(commit_sha)
end

commit_sha
ensure
@lock.close if @lock
@lock = nil
File.unlink("#{repo.path}/refs/heads/#{head}.lock") rescue nil
end

class Tree
include Enumerable
def put_raw_object(data, type)
repo.git.ruby_git.put_raw_object(data, type)
end

attr_reader :data
attr_accessor :name
def write_blob(blob)
return if not blob.modified?

def initialize(name = nil)
@data = {}
@name = name
end

def load(tree)
@name = tree.name
@data = tree.contents.inject({}) do |hash, file|
if file.is_a?(Grit::Tree)
hash[file.name] = (@data[file.name] || Tree.new).load(file)
else
hash[file.name] = Blob.new(file)
end
hash
end
self
end

def inspect
"#<GitStore::Tree #{@data.inspect}>"
end

def fetch(name)
name = name.to_s
entry = @data[name]
blob.sha1 = put_raw_object(blob.serialize, 'blob')
blob.modified = false
end

def write_tree(tree)
return if not tree.modified?

contents = tree.data.map do |name, entry|
case entry
when Blob then entry.data
when Tree then entry
end
end

def store(name, value)
name = name.to_s
if value.is_a?(Tree)
value.name = name
@data[name] = value
else
@data[name] = Blob.new(name, value)
when Blob; write_blob(entry)
when Tree; write_tree(entry)
end
"%s %s\0%s" % [entry.mode, name, [entry.sha1].pack("H*")]
end

def has_key?(name)
@data.has_key?(name)
end
tree.modified = false
tree.sha1 = put_raw_object(contents.join, 'tree')
end

class FileStore < GitStore

def [](*args)
args = args.first.to_s.split('/') if args.size == 1
args.inject(self) { |tree, key| tree.fetch(key) or return nil }
attr_reader :path

def initialize(path = '.')
@path = path
@root = Tree.new('')
end

def []=(*args)
value = args.pop
args = args.first.to_s.split('/') if args.size == 1
tree = args[0..-2].to_a.inject(self) do |tree, key|
tree.has_key?(key) ? tree.fetch(key) : tree.store(key, Tree.new(key))
end
tree.store(args.last, value)
def load
root.load_from_disk
end

def delete(name)
@data.delete(name)
end

def each(&block)
@data.values.each do |entry|
case entry
when Blob then yield entry.data
when Tree then entry.each(&block)
end
def refresh!
root.each_blob do |blob|

end
end

def each_with_path(path = [], &block)
@data.each do |name, entry|
child_path = path + [name]
case entry
when Blob then yield entry, child_path.join('/')
when Tree then entry.each_with_path(child_path, &block)
end
end
end

def to_hash
@data.inject({}) do |hash, (name, entry)|
hash[name] = entry.is_a?(Tree) ? entry.to_hash : entry.to_s
hash
end
def commit(message="")
root.write_to_disk
end

end

attr_reader :repo, :index, :tree

def initialize(path, &block)
@repo = Grit::Repo.new(path)
@index = Grit::Index.new(@repo)
@tree = Tree.new
end

def commit(message="")
index.tree = tree.to_hash
head = repo.heads.first
index.commit(message, head ? head.commit.id : nil)
end

def [](*args)
tree[*args]
end

def []=(*args)
value = args.pop
tree[*args] = value
end

def delete(path)
tree.delete(path)
end

def load
tree.load(repo.tree)
end

def each(&block)
tree.each(&block)
end

def each_with_path(&block)
tree.each_with_path(&block)
end

end

0 comments on commit 9dcbdf9

Please sign in to comment.