git_store 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *~
2
+ doc/*
3
+ pkg/*
4
+ test/repo
5
+ html/*
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2008 Matthias Georgi <http://www.matthias-georgi.de>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,147 @@
1
+ Git Store - using Git as versioned data store in Ruby
2
+ =====================================================
3
+
4
+ GitStore implements a versioned data store based on the revision
5
+ management system [Git][1]. You can store object hierarchies as nested
6
+ hashes, which will be mapped on the directory structure of a git
7
+ repository. Basically GitStore checks out the repository into a
8
+ in-memory representation, which can be modified and finally committed.
9
+
10
+ GitStore supports transactions, so that updates to the store either
11
+ fail or succeed completely.
12
+
13
+ ### Installation
14
+
15
+ GitStore can be installed as gem easily:
16
+
17
+ $ gem sources -a http://gems.github.com
18
+ $ sudo gem install georgi-git_store
19
+
20
+ ### Usage Example
21
+
22
+ First thing you should do, is to initialize a new git repository.
23
+
24
+ $ mkdir test
25
+ $ cd test
26
+ $ git init
27
+
28
+ Now you can instantiate a GitStore instance and store some data. The
29
+ data will be serialized depending on the file extension. So for YAML
30
+ storage you can use the 'yml' extension:
31
+
32
+ store = GitStore.new('/path/to/repo')
33
+
34
+ store['users/matthias.yml'] = User.new('Matthias')
35
+ store['pages/home.yml'] = Page.new('matthias', 'Home')
36
+
37
+ store.commit 'Added user and page'
38
+
39
+ ### Transactions
40
+
41
+ GitStore manages concurrent access by a file locking scheme. So only
42
+ one process can start a transaction at one time. This is implemented
43
+ by locking the `refs/head/<branch>.lock` file, which is also
44
+ respected by the git binary.
45
+
46
+ If you access the repository from different processes or threads, you
47
+ should write to the store using transactions. If something goes wrong
48
+ inside a transaction, all changes will be rolled back to the original
49
+ state.
50
+
51
+ store = GitStore.new('/path/to/repo')
52
+
53
+ store.transaction do
54
+ # If an exception happens here, the transaction will be aborted.
55
+ store['pages/home.yml'] = Page.new('matthias', 'Home')
56
+ end
57
+
58
+
59
+ A transaction without a block looks like this:
60
+
61
+ store.start_transaction
62
+
63
+ store['pages/home.yml'] = Page.new('matthias', 'Home')
64
+
65
+ store.rollback # This will restore the original state
66
+
67
+
68
+ ### Data Storage
69
+
70
+ When you call the `commit` method, your data is written back straight
71
+ into the git repository. No intermediate file representation. So if
72
+ you want to have a look at your data, you can use a git browser like
73
+ [git-gui][6] or checkout the files:
74
+
75
+ $ git checkout
76
+
77
+
78
+ ### Iteration
79
+
80
+ Iterating over the data objects is quite easy. Furthermore you can
81
+ iterate over trees and subtrees, so you can partition your data in a
82
+ meaningful way. For example you may separate the config files and the
83
+ pages of a wiki:
84
+
85
+ store['pages/home.yml'] = Page.new('matthias', 'Home')
86
+ store['pages/about.yml'] = Page.new('matthias', 'About')
87
+ store['config/wiki.yml'] = { 'name' => 'My Personal Wiki' }
88
+
89
+ # Enumerate all objects
90
+ store.each { |obj| ... }
91
+
92
+ # Enumerate only pages
93
+ store['pages'].each { |page| ... }
94
+
95
+
96
+ ### Serialization
97
+
98
+ Serialization is dependent on the filename extension. You can add more
99
+ handlers if you like, the interface is like this:
100
+
101
+ class YAMLHandler
102
+ def read(data)
103
+ YAML.load(data)
104
+ end
105
+
106
+ def write(data)
107
+ data.to_yaml
108
+ end
109
+ end
110
+
111
+ Shinmun uses its own handler for files with `md` extension:
112
+
113
+ class PostHandler
114
+ def read(data)
115
+ Post.new(:src => data)
116
+ end
117
+
118
+ def write(post)
119
+ post.dump
120
+ end
121
+ end
122
+
123
+ store = GitStore.new('.')
124
+ store.handler['md'] = PostHandler.new
125
+
126
+
127
+ ### GitStore on GitHub
128
+
129
+ Download or fork the project on its [Github page][5]
130
+
131
+ ### Mailing List
132
+
133
+ Please join the [GitStore Google Group][3] for further discussion.
134
+
135
+ ### Related Work
136
+
137
+ John Wiegley already has done [something similar for Python][4].
138
+
139
+
140
+
141
+ [1]: http://git.or.cz/
142
+ [2]: http://github.com/mojombo/grit
143
+ [3]: http://groups.google.com/group/gitstore
144
+ [4]: http://www.newartisans.com/blog_files/git.versioned.data.store.php
145
+ [5]: http://github.com/georgi/git_store
146
+ [6]: http://www.kernel.org/pub/software/scm/git/docs/git-gui.html
147
+ [7]: http://www.matthias-georgi.de/shinmun
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ require 'rake'
2
+ require "rake/rdoctask"
3
+
4
+ begin
5
+ require 'spec/rake/spectask'
6
+ rescue LoadError
7
+ puts <<-EOS
8
+ To use rspec for testing you must install the rspec gem:
9
+ gem install rspec
10
+ EOS
11
+ exit(0)
12
+ end
13
+
14
+ desc "Run all specs"
15
+ Spec::Rake::SpecTask.new(:spec) do |t|
16
+ t.spec_opts = ['-cfs']
17
+ t.spec_files = FileList['test/**/*_spec.rb']
18
+ end
19
+
20
+ desc "Print SpecDocs"
21
+ Spec::Rake::SpecTask.new(:doc) do |t|
22
+ t.spec_opts = ["--format", "specdoc"]
23
+ t.spec_files = FileList['test/*_spec.rb']
24
+ end
25
+
26
+ desc "Generate the RDoc"
27
+ Rake::RDocTask.new do |rdoc|
28
+ files = ["README.md", "LICENSE", "lib/**/*.rb"]
29
+ rdoc.rdoc_files.add(files)
30
+ rdoc.main = "README.md"
31
+ rdoc.title = "Git Store - using Git as versioned data store in Ruby"
32
+ end
33
+
34
+ desc "Run the rspec"
35
+ task :default => :spec
data/git_store.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'git_store'
3
+ s.version = '0.3.1'
4
+ s.summary = 'a simple data store based on git'
5
+ s.author = 'Matthias Georgi'
6
+ s.email = 'matti.georgi@gmail.com'
7
+ s.homepage = 'http://ww.matthias-georgi.de/git_store'
8
+ s.description = <<END
9
+ GitStore implements a versioned data store based on the revision
10
+ management system Git. You can store object hierarchies as nested
11
+ hashes, which will be mapped on the directory structure of a git
12
+ repository. GitStore checks out the repository into a in-memory
13
+ representation, which can be modified and finally committed.
14
+ END
15
+ s.require_path = 'lib'
16
+ s.has_rdoc = true
17
+ s.extra_rdoc_files = ['README.md']
18
+ s.files = %w{
19
+ .gitignore
20
+ LICENSE
21
+ README.md
22
+ Rakefile
23
+ git_store.gemspec
24
+ lib/git_store.rb
25
+ lib/git_store/blob.rb
26
+ lib/git_store/commit.rb
27
+ lib/git_store/diff.rb
28
+ lib/git_store/handlers.rb
29
+ lib/git_store/pack.rb
30
+ lib/git_store/tag.rb
31
+ lib/git_store/tree.rb
32
+ lib/git_store/user.rb
33
+ test/bare_store_spec.rb
34
+ test/benchmark.rb
35
+ test/commit_spec.rb
36
+ test/git_store_spec.rb
37
+ test/tree_spec.rb
38
+ }
39
+ end
40
+
@@ -0,0 +1,32 @@
1
+ class GitStore
2
+
3
+ # This class stores the raw string data of a blob, but also the
4
+ # deserialized data object.
5
+ class Blob
6
+
7
+ attr_accessor :store, :id, :data, :mode, :object
8
+
9
+ # Initialize a Blob
10
+ def initialize(store, id = nil, data = nil)
11
+ @store = store
12
+ @id = id || store.id_for('blob', data)
13
+ @data = data
14
+ @mode = "100644"
15
+ end
16
+
17
+ def ==(other)
18
+ Blob === other and id == other.id
19
+ end
20
+
21
+ def dump
22
+ @data
23
+ end
24
+
25
+ # Write the data to the git object store
26
+ def write
27
+ @id = store.put(self)
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,65 @@
1
+ class GitStore
2
+
3
+ class Commit
4
+ attr_accessor :store, :id, :tree, :parent, :author, :committer, :message
5
+
6
+ def initialize(store, id = nil, data = nil)
7
+ @store = store
8
+ @id = id
9
+ @parent = []
10
+
11
+ parse(data) if data
12
+ end
13
+
14
+ def ==(other)
15
+ Commit === other and id == other.id
16
+ end
17
+
18
+ def parse(data)
19
+ headers, @message = data.split(/\n\n/, 2)
20
+
21
+ headers.split(/\n/).each do |header|
22
+ key, value = header.split(/ /, 2)
23
+ case key
24
+ when 'parent'
25
+ @parent << value
26
+
27
+ when 'author'
28
+ @author = User.parse(value)
29
+
30
+ when 'committer'
31
+ @committer = User.parse(value)
32
+
33
+ when 'tree'
34
+ @tree = store.get(value)
35
+ end
36
+ end
37
+
38
+ self
39
+ end
40
+
41
+ def diff(commit, path = nil)
42
+ commit = commit.id if Commit === commit
43
+ Diff.exec(store, "git diff --full-index #{commit} #{id} -- '#{path}'")
44
+ end
45
+
46
+ def diffs(path = nil)
47
+ diff(parent.first, path)
48
+ end
49
+
50
+ def write
51
+ @id = store.put(self)
52
+ end
53
+
54
+ def dump
55
+ [ "tree #{ tree.id }",
56
+ parent.map { |parent| "parent #{parent}" },
57
+ "author #{ author.dump }",
58
+ "committer #{ committer.dump }",
59
+ '',
60
+ message ].flatten.join("\n")
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,76 @@
1
+ class GitStore
2
+
3
+ # adapted from Grit
4
+ class Diff
5
+ attr_reader :store
6
+ attr_reader :a_path, :b_path
7
+ attr_reader :a_blob, :b_blob
8
+ attr_reader :a_mode, :b_mode
9
+ attr_reader :new_file, :deleted_file
10
+ attr_reader :diff
11
+
12
+ def initialize(store, a_path, b_path, a_blob, b_blob, a_mode, b_mode, new_file, deleted_file, diff)
13
+ @store = store
14
+ @a_path = a_path
15
+ @b_path = b_path
16
+ @a_blob = a_blob =~ /^0{40}$/ ? nil : store.get(a_blob)
17
+ @b_blob = b_blob =~ /^0{40}$/ ? nil : store.get(b_blob)
18
+ @a_mode = a_mode
19
+ @b_mode = b_mode
20
+ @new_file = new_file
21
+ @deleted_file = deleted_file
22
+ @diff = diff
23
+ end
24
+
25
+ def self.exec(store, cmd)
26
+ list(store, IO.popen(cmd) { |io| io.read })
27
+ end
28
+
29
+ def self.list(store, text)
30
+ lines = text.split("\n")
31
+
32
+ diffs = []
33
+
34
+ while !lines.empty?
35
+ m, a_path, b_path = *lines.shift.match(%r{^diff --git a/(.+?) b/(.+)$})
36
+
37
+ if lines.first =~ /^old mode/
38
+ m, a_mode = *lines.shift.match(/^old mode (\d+)/)
39
+ m, b_mode = *lines.shift.match(/^new mode (\d+)/)
40
+ end
41
+
42
+ if lines.empty? || lines.first =~ /^diff --git/
43
+ diffs << Diff.new(store, a_path, b_path, nil, nil, a_mode, b_mode, false, false, nil)
44
+ next
45
+ end
46
+
47
+ new_file = false
48
+ deleted_file = false
49
+
50
+ if lines.first =~ /^new file/
51
+ m, b_mode = lines.shift.match(/^new file mode (.+)$/)
52
+ a_mode = nil
53
+ new_file = true
54
+ elsif lines.first =~ /^deleted file/
55
+ m, a_mode = lines.shift.match(/^deleted file mode (.+)$/)
56
+ b_mode = nil
57
+ deleted_file = true
58
+ end
59
+
60
+ m, a_blob, b_blob, b_mode = *lines.shift.match(%r{^index ([0-9A-Fa-f]+)\.\.([0-9A-Fa-f]+) ?(.+)?$})
61
+ b_mode.strip! if b_mode
62
+
63
+ diff_lines = []
64
+ while lines.first && lines.first !~ /^diff/
65
+ diff_lines << lines.shift
66
+ end
67
+ diff = diff_lines.join("\n")
68
+
69
+ diffs << Diff.new(store, a_path, b_path, a_blob, b_blob, a_mode, b_mode, new_file, deleted_file, diff)
70
+ end
71
+
72
+ diffs
73
+ end
74
+ end
75
+
76
+ end
@@ -0,0 +1,36 @@
1
+
2
+ # This fix ensures sorted yaml maps.
3
+ class Hash
4
+ def to_yaml( opts = {} )
5
+ YAML::quick_emit( object_id, opts ) do |out|
6
+ out.map( taguri, to_yaml_style ) do |map|
7
+ sort_by { |k, v| k.to_s }.each do |k, v|
8
+ map.add( k, v )
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ class GitStore
16
+
17
+ class DefaultHandler
18
+ def read(data)
19
+ data
20
+ end
21
+
22
+ def write(data)
23
+ data.to_s
24
+ end
25
+ end
26
+
27
+ class YAMLHandler
28
+ def read(data)
29
+ YAML.load(data)
30
+ end
31
+
32
+ def write(data)
33
+ data.to_yaml
34
+ end
35
+ end
36
+ end