gitki 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. data/ChangeLog +0 -0
  2. data/README.rdoc +74 -0
  3. data/Rakefile +59 -0
  4. data/app.rb +90 -0
  5. data/bin/gitki +3 -0
  6. data/bin/gitki.ru +13 -0
  7. data/config.ru +3 -0
  8. data/console +13 -0
  9. data/lib/gitki.png +0 -0
  10. data/lib/gitki.rb +116 -0
  11. data/lib/home_template.haml +17 -0
  12. data/lib/navigation_template.haml +4 -0
  13. data/public/background.png +0 -0
  14. data/public/favicon.ico +0 -0
  15. data/setting.yml +3 -0
  16. data/spec/gitki_spec.rb +104 -0
  17. data/vendor/git_store/LICENSE +18 -0
  18. data/vendor/git_store/README.md +147 -0
  19. data/vendor/git_store/Rakefile +35 -0
  20. data/vendor/git_store/TODO +3 -0
  21. data/vendor/git_store/git_store.gemspec +40 -0
  22. data/vendor/git_store/lib/git_store.rb +373 -0
  23. data/vendor/git_store/lib/git_store/blob.rb +32 -0
  24. data/vendor/git_store/lib/git_store/commit.rb +65 -0
  25. data/vendor/git_store/lib/git_store/diff.rb +76 -0
  26. data/vendor/git_store/lib/git_store/handlers.rb +36 -0
  27. data/vendor/git_store/lib/git_store/pack.rb +425 -0
  28. data/vendor/git_store/lib/git_store/tag.rb +40 -0
  29. data/vendor/git_store/lib/git_store/tree.rb +183 -0
  30. data/vendor/git_store/lib/git_store/user.rb +29 -0
  31. data/vendor/git_store/test/bare_store_spec.rb +33 -0
  32. data/vendor/git_store/test/benchmark.rb +30 -0
  33. data/vendor/git_store/test/commit_spec.rb +81 -0
  34. data/vendor/git_store/test/git_store_spec.rb +257 -0
  35. data/vendor/git_store/test/helper.rb +18 -0
  36. data/vendor/git_store/test/tree_spec.rb +92 -0
  37. data/views/layout.haml +23 -0
  38. data/views/page.haml +7 -0
  39. data/views/pages.haml +9 -0
  40. data/views/styles.sass +87 -0
  41. metadata +103 -0
@@ -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.
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ * File history
2
+ * File diffs
3
+ * Retrieving all paths of a repository.
@@ -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,373 @@
1
+ require 'rubygems'
2
+ require 'zlib'
3
+ require 'digest/sha1'
4
+ require 'yaml'
5
+ require 'fileutils'
6
+
7
+ require 'git_store/blob'
8
+ require 'git_store/diff'
9
+ require 'git_store/tree'
10
+ require 'git_store/tag'
11
+ require 'git_store/user'
12
+ require 'git_store/pack'
13
+ require 'git_store/commit'
14
+ require 'git_store/handlers'
15
+
16
+ # GitStore implements a versioned data store based on the revision
17
+ # management system git. You can store object hierarchies as nested
18
+ # hashes, which will be mapped on the directory structure of a git
19
+ # repository.
20
+ #
21
+ # GitStore supports transactions, so that updates to the store either
22
+ # fail or succeed completely.
23
+ #
24
+ # GitStore manages concurrent access by a file locking scheme. So only
25
+ # one process can start a transaction at one time. This is implemented
26
+ # by locking the `refs/head/<branch>.lock` file, which is also respected
27
+ # by the git binary.
28
+ #
29
+ # A regular commit should be atomic by the nature of git, as the only
30
+ # critical part is writing the 40 bytes SHA1 hash of the commit object
31
+ # to the file `refs/head/<branch>`, which is done atomically by the
32
+ # operating system.
33
+ #
34
+ # So reading a repository should be always consistent in a git
35
+ # repository. The head of a branch points to a commit object, which in
36
+ # turn points to a tree object, which itself is a snapshot of the
37
+ # GitStore at commit time. All involved objects are keyed by their
38
+ # SHA1 value, so there is no chance for another process to write to
39
+ # the same files.
40
+ #
41
+ class GitStore
42
+ include Enumerable
43
+
44
+ TYPE_CLASS = {
45
+ 'tree' => Tree,
46
+ 'blob' => Blob,
47
+ 'commit' => Commit,
48
+ 'tag' => Tag
49
+ }
50
+
51
+ CLASS_TYPE = {
52
+ Tree => 'tree',
53
+ Blob => 'blob',
54
+ Commit => 'commit',
55
+ Tag => 'tag'
56
+ }
57
+
58
+ attr_reader :path, :index, :root, :branch, :lock_file, :head, :packs, :handler, :bare, :objects
59
+
60
+ # Initialize a store.
61
+ def initialize(path, branch = 'master', bare = false)
62
+ if bare && !File.exists?("#{path}") or
63
+ !bare && !File.exists?("#{path}/.git")
64
+ raise ArgumentError, "first argument must be a valid Git repository: `#{path}'"
65
+ end
66
+
67
+ @bare = bare
68
+ @path = path.chomp('/')
69
+ @branch = branch
70
+ @root = Tree.new(self)
71
+ @packs = {}
72
+ @objects = {}
73
+
74
+ init_handler
75
+
76
+ load_packs("#{git_path}/objects/pack")
77
+ load
78
+ end
79
+
80
+ def init_handler
81
+ @handler = {
82
+ 'yml' => YAMLHandler.new
83
+ }
84
+ @handler.default = DefaultHandler.new
85
+ end
86
+
87
+ # The path to the current head file.
88
+ def head_path
89
+ "#{git_path}/refs/heads/#{branch}"
90
+ end
91
+
92
+ # The path to the object file for given id.
93
+ def object_path(id)
94
+ "#{git_path}/objects/#{ id[0...2] }/#{ id[2..39] }"
95
+ end
96
+
97
+ def git_path
98
+ if bare
99
+ "#{path}"
100
+ else
101
+ "#{path}/.git"
102
+ end
103
+ end
104
+
105
+ # Read the id of the head commit.
106
+ #
107
+ # Returns the object id of the last commit.
108
+ def read_head_id
109
+ File.read(head_path).strip if File.exists?(head_path)
110
+ end
111
+
112
+ def handler_for(path)
113
+ handler[ path.split('.').last ]
114
+ end
115
+
116
+ # Read an object for the specified path.
117
+ def [](path)
118
+ root[path]
119
+ end
120
+
121
+ # Write an object to the specified path.
122
+ def []=(path, data)
123
+ root[path] = data
124
+ end
125
+
126
+ # Iterate over all key-values pairs found in this store.
127
+ def each(&block)
128
+ root.each(&block)
129
+ end
130
+
131
+ def paths
132
+ root.paths
133
+ end
134
+
135
+ def values
136
+ root.values
137
+ end
138
+
139
+ def delete(path)
140
+ root.delete(path)
141
+ end
142
+
143
+ def tree(name)
144
+ root.tree(name)
145
+ end
146
+
147
+ # Returns the store as a hash tree.
148
+ def to_hash
149
+ root.to_hash
150
+ end
151
+
152
+ # Inspect the store.
153
+ def inspect
154
+ "#<GitStore #{path} #{branch}>"
155
+ end
156
+
157
+ # Has our store been changed on disk?
158
+ def changed?
159
+ head.nil? or head.id != read_head_id
160
+ end
161
+
162
+ # Load the current head version from repository.
163
+ def load(from_disk = false)
164
+ if id = read_head_id
165
+ @head = get(id)
166
+ @root = @head.tree
167
+ end
168
+
169
+ load_from_disk if from_disk
170
+ end
171
+
172
+ def load_from_disk
173
+ root.each_blob do |path, blob|
174
+ file = "#{self.path}/#{path}"
175
+ if File.file?(file)
176
+ blob.data = File.read(file)
177
+ end
178
+ end
179
+ end
180
+
181
+ # Reload the store, if it has been changed on disk.
182
+ def refresh!
183
+ load if changed?
184
+ end
185
+
186
+ # Is there any transaction going on?
187
+ def in_transaction?
188
+ Thread.current['git_store_lock']
189
+ end
190
+
191
+ # All changes made inside a transaction are atomic. If some
192
+ # exception occurs the transaction will be rolled back.
193
+ #
194
+ # Example:
195
+ # store.transaction { store['a'] = 'b' }
196
+ #
197
+ def transaction(message = "")
198
+ start_transaction
199
+ result = yield
200
+ commit message
201
+
202
+ result
203
+ rescue
204
+ rollback
205
+ raise
206
+ ensure
207
+ finish_transaction
208
+ end
209
+
210
+ # Start a transaction.
211
+ #
212
+ # Tries to get lock on lock file, reload the this store if
213
+ # has changed in the repository.
214
+ def start_transaction
215
+ file = open("#{head_path}.lock", "w")
216
+ file.flock(File::LOCK_EX)
217
+
218
+ Thread.current['git_store_lock'] = file
219
+
220
+ load if changed?
221
+ end
222
+
223
+ # Restore the state of the store.
224
+ #
225
+ # Any changes made to the store are discarded.
226
+ def rollback
227
+ objects.clear
228
+ load
229
+ finish_transaction
230
+ end
231
+
232
+ # Finish the transaction.
233
+ #
234
+ # Release the lock file.
235
+ def finish_transaction
236
+ Thread.current['git_store_lock'].close rescue nil
237
+ Thread.current['git_store_lock'] = nil
238
+
239
+ File.unlink("#{head_path}.lock") rescue nil
240
+ end
241
+
242
+ # Write the commit object to disk and set the head of the current branch.
243
+ #
244
+ # Returns the id of the commit object
245
+ def commit(message = '', author = User.from_config, committer = author)
246
+ root.write
247
+
248
+ commit = Commit.new(self)
249
+ commit.tree = root
250
+ commit.parent << head.id if head
251
+ commit.author = author
252
+ commit.committer = committer
253
+ commit.message = message
254
+ commit.write
255
+
256
+ open(head_path, "wb") do |file|
257
+ file.write(commit.id)
258
+ end
259
+
260
+ @head = commit
261
+ end
262
+
263
+ def commits(limit = 10, start = head)
264
+ entries = []
265
+ current = start
266
+
267
+ while current and entries.size < limit
268
+ entries << current
269
+ current = get(current.parent.first)
270
+ end
271
+
272
+ entries
273
+ end
274
+
275
+ def get(id)
276
+ return nil if id.nil?
277
+
278
+ return objects[id] if objects.has_key?(id)
279
+
280
+ type, content = get_object(id)
281
+
282
+ klass = TYPE_CLASS[type] or raise NotImplementedError, "type not supported: #{type}"
283
+
284
+ objects[id] = klass.new(self, id, content)
285
+ end
286
+
287
+ def put(object)
288
+ type = CLASS_TYPE[object.class] or raise NotImplementedError, "class not supported: #{object.class}"
289
+
290
+ id = put_object(type, object.dump)
291
+
292
+ objects[id] = object
293
+
294
+ id
295
+ end
296
+
297
+ # Returns the hash value of an object string.
298
+ def sha(str)
299
+ Digest::SHA1.hexdigest(str)[0, 40]
300
+ end
301
+
302
+ def id_for(type, content)
303
+ sha "#{type} #{content.length}\0#{content}"
304
+ end
305
+
306
+ # Read the raw object with the given id from the repository.
307
+ #
308
+ # Returns a pair of content and type of the object
309
+ def get_object(id)
310
+ path = object_path(id)
311
+
312
+ if File.exists?(path)
313
+ buf = open(path, "rb") { |f| f.read }
314
+
315
+ raise "not a loose object: #{id}" if not legacy_loose_object?(buf)
316
+
317
+ header, content = Zlib::Inflate.inflate(buf).split(/\0/, 2)
318
+ type, size = header.split(/ /, 2)
319
+
320
+ raise "bad object: #{id}" if content.length != size.to_i
321
+ else
322
+ content, type = get_object_from_pack(id)
323
+ end
324
+
325
+ return type, content
326
+ end
327
+
328
+ # Write a raw object to the repository.
329
+ #
330
+ # Returns the object id.
331
+ def put_object(type, content)
332
+ data = "#{type} #{content.length}\0#{content}"
333
+ id = sha(data)
334
+ path = object_path(id)
335
+
336
+ unless File.exists?(path)
337
+ FileUtils.mkpath(File.dirname(path))
338
+ open(path, 'wb') do |f|
339
+ f.write Zlib::Deflate.deflate(data)
340
+ end
341
+ end
342
+
343
+ id
344
+ end
345
+
346
+ def legacy_loose_object?(buf)
347
+ word = (buf[0] << 8) + buf[1]
348
+
349
+ buf[0] == 0x78 && word % 31 == 0
350
+ end
351
+
352
+ def get_object_from_pack(id)
353
+ pack, offset = @packs[id]
354
+
355
+ pack.parse_object(offset) if pack
356
+ end
357
+
358
+ def load_packs(path)
359
+ if File.directory?(path)
360
+ Dir.open(path) do |dir|
361
+ entries = dir.select { |entry| entry =~ /\.pack$/i }
362
+ entries.each do |entry|
363
+ pack = PackStorage.new(File.join(path, entry))
364
+ pack.each_entry do |id, offset|
365
+ id = id.unpack("H*").first
366
+ @packs[id] = [pack, offset]
367
+ end
368
+ end
369
+ end
370
+ end
371
+ end
372
+
373
+ end