forked from georgi/git_store
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
better concurrency. better scaling. 20000 entries in 1000 folders sti…
…ll performant.
- Loading branch information
Showing
7 changed files
with
477 additions
and
222 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.