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,40 @@
1
+ class GitStore
2
+
3
+ class Tag
4
+ attr_accessor :store, :id, :object, :type, :tagger, :message
5
+
6
+ def initialize(store, id = nil, data = nil)
7
+ @store = store
8
+ @id = id
9
+
10
+ parse(data) if data
11
+ end
12
+
13
+ def ==(other)
14
+ Tag === other and id == other.id
15
+ end
16
+
17
+ def parse(data)
18
+ headers, @message = data.split(/\n\n/, 2)
19
+
20
+ headers.split(/\n/).each do |header|
21
+ key, value = header.split(/ /, 2)
22
+ case key
23
+ when 'type'
24
+ @type = value
25
+
26
+ when 'object'
27
+ @object = store.get(value)
28
+
29
+ when 'tagger'
30
+ @tagger = User.parse(value)
31
+
32
+ end
33
+ end
34
+
35
+ self
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,183 @@
1
+ class GitStore
2
+
3
+ class Tree
4
+ include Enumerable
5
+
6
+ attr_reader :store, :table
7
+ attr_accessor :id, :data, :mode
8
+
9
+ # Initialize a tree
10
+ def initialize(store, id = nil, data = nil)
11
+ @store = store
12
+ @id = id
13
+ @table = {}
14
+ @mode = "040000"
15
+ parse(data) if data
16
+ end
17
+
18
+ def ==(other)
19
+ Tree === other and id == other.id
20
+ end
21
+
22
+ # Has this tree been modified?
23
+ def modified?
24
+ @modified or @table.values.any? { |entry| Tree === entry and entry.modified? }
25
+ end
26
+
27
+ # Find or create a subtree with specified name.
28
+ def tree(name)
29
+ get(name) or put(name, Tree.new(store))
30
+ end
31
+
32
+ # Read the contents of a raw git object.
33
+ def parse(data)
34
+ @table.clear
35
+
36
+ while data.size > 0
37
+ mode, data = data.split(" ", 2)
38
+ name, data = data.split("\0", 2)
39
+ id = data.slice!(0, 20).unpack("H*").first
40
+
41
+ @table[name] = store.get(id)
42
+ end
43
+ end
44
+
45
+ def dump
46
+ @table.map { |k, v| "#{ v.mode } #{ k }\0#{ [v.write].pack("H*") }" }.join
47
+ end
48
+
49
+ # Write this tree back to the git repository.
50
+ #
51
+ # Returns the object id of the tree.
52
+ def write
53
+ return id if not modified?
54
+ @modified = false
55
+ @id = store.put(self)
56
+ end
57
+
58
+ # Read entry with specified name.
59
+ def get(name)
60
+ entry = @table[name]
61
+
62
+ case entry
63
+ when Blob
64
+ entry.object ||= handler_for(name).read(entry.data)
65
+
66
+ when Tree
67
+ entry
68
+ end
69
+ end
70
+
71
+ def handler_for(name)
72
+ store.handler_for(name)
73
+ end
74
+
75
+ # Write entry with specified name.
76
+ def put(name, value)
77
+ @modified = true
78
+
79
+ if value.is_a?(Tree)
80
+ @table[name] = value
81
+ else
82
+ @table[name] = Blob.new(store, nil, handler_for(name).write(value))
83
+ end
84
+
85
+ value
86
+ end
87
+
88
+ # Remove entry with specified name.
89
+ def remove(name)
90
+ @modified = true
91
+ @table.delete(name.to_s)
92
+ end
93
+
94
+ # Does this key exist in the table?
95
+ def has_key?(name)
96
+ @table.has_key?(name.to_s)
97
+ end
98
+
99
+ def normalize_path(path)
100
+ (path[0, 1] == '/' ? path[1..-1] : path).split('/')
101
+ end
102
+
103
+ # Read a value on specified path.
104
+ def [](path)
105
+ normalize_path(path).inject(self) do |tree, key|
106
+ tree.get(key) or return nil
107
+ end
108
+ end
109
+
110
+ # Write a value on specified path.
111
+ def []=(path, value)
112
+ list = normalize_path(path)
113
+ tree = list[0..-2].to_a.inject(self) { |tree, name| tree.tree(name) }
114
+ tree.put(list.last, value)
115
+ end
116
+
117
+ # Delete a value on specified path.
118
+ def delete(path)
119
+ list = normalize_path(path)
120
+
121
+ tree = list[0..-2].to_a.inject(self) do |tree, key|
122
+ tree.get(key) or return
123
+ end
124
+
125
+ tree.remove(list.last)
126
+ end
127
+
128
+ # Iterate over all objects found in this subtree.
129
+ def each(path = [], &block)
130
+ @table.sort.each do |name, entry|
131
+ child_path = path + [name]
132
+ case entry
133
+ when Blob
134
+ entry.object ||= handler_for(name).read(entry.data)
135
+ yield child_path.join("/"), entry.object
136
+
137
+ when Tree
138
+ entry.each(child_path, &block)
139
+ end
140
+ end
141
+ end
142
+
143
+ def each_blob(path = [], &block)
144
+ @table.sort.each do |name, entry|
145
+ child_path = path + [name]
146
+
147
+ case entry
148
+ when Blob
149
+ yield child_path.join("/"), entry
150
+
151
+ when Tree
152
+ entry.each_blob(child_path, &block)
153
+ end
154
+ end
155
+ end
156
+
157
+ def paths
158
+ map { |path, data| path }
159
+ end
160
+
161
+ def values
162
+ map { |path, data| data }
163
+ end
164
+
165
+ # Convert this tree into a hash object.
166
+ def to_hash
167
+ @table.inject({}) do |hash, (name, entry)|
168
+ if entry.is_a?(Tree)
169
+ hash[name] = entry.to_hash
170
+ else
171
+ hash[name] = entry.object ||= handler_for(name).read(entry.data)
172
+ end
173
+ hash
174
+ end
175
+ end
176
+
177
+ def inspect
178
+ "#<GitStore::Tree #{id} #{mode} #{to_hash.inspect}>"
179
+ end
180
+
181
+ end
182
+
183
+ end
@@ -0,0 +1,29 @@
1
+ class GitStore
2
+
3
+ class User
4
+ attr_accessor :name, :email, :time
5
+
6
+ def initialize(name, email, time)
7
+ @name, @email, @time = name, email, time
8
+ end
9
+
10
+ def dump
11
+ "#{ name } <#{email}> #{ time.to_i } #{ time.strftime('%z') }"
12
+ end
13
+
14
+ def self.from_config
15
+ name = IO.popen("git config user.name") { |io| io.gets.chomp }
16
+ email = IO.popen("git config user.email") { |io| io.gets.chomp }
17
+
18
+ new name, email, Time.now
19
+ end
20
+
21
+ def self.parse(user)
22
+ if match = user.match(/(.*)<(.*)> (\d+) ([+-]\d+)/)
23
+ new match[1].strip, match[2].strip, Time.at(match[3].to_i + match[4].to_i * 3600)
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,33 @@
1
+ require "#{File.dirname(__FILE__)}/../lib/git_store"
2
+ require "#{File.dirname(__FILE__)}/helper"
3
+ require 'pp'
4
+
5
+ describe GitStore do
6
+
7
+ REPO = '/tmp/git_store_test.git'
8
+
9
+ attr_reader :store
10
+
11
+ before(:each) do
12
+ FileUtils.rm_rf REPO
13
+ Dir.mkdir REPO
14
+ Dir.chdir REPO
15
+
16
+ `git init --bare`
17
+ @store = GitStore.new(REPO, 'master', true)
18
+ end
19
+
20
+ it 'should fail to initialize without a valid git repository' do
21
+ lambda {
22
+ GitStore.new('/foo', 'master', true)
23
+ }.should raise_error(ArgumentError)
24
+ end
25
+
26
+ it 'should save and load entries' do
27
+ store['a'] = 'Hello'
28
+ store.commit
29
+ store.load
30
+
31
+ store['a'].should == 'Hello'
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ require 'git_store'
2
+ require 'grit'
3
+ require 'benchmark'
4
+ require 'fileutils'
5
+
6
+ REPO = '/tmp/git-store'
7
+
8
+ FileUtils.rm_rf REPO
9
+ FileUtils.mkpath REPO
10
+ Dir.chdir REPO
11
+
12
+ `git init`
13
+
14
+ store = GitStore.new(REPO)
15
+
16
+ Benchmark.bm 20 do |x|
17
+ x.report 'store 1000 objects' do
18
+ store.transaction { 'aaa'.upto('jjj') { |key| store[key] = rand.to_s } }
19
+ end
20
+ x.report 'commit one object' do
21
+ store.transaction { store['aa'] = rand.to_s }
22
+ end
23
+ x.report 'load 1000 objects' do
24
+ GitStore.new('.').values { |v| v }
25
+ end
26
+ x.report 'load 1000 with grit' do
27
+ Grit::Repo.new('.').tree.contents.each { |e| e.data }
28
+ end
29
+ end
30
+
@@ -0,0 +1,81 @@
1
+ require "#{File.dirname(__FILE__)}/../lib/git_store"
2
+ require 'pp'
3
+
4
+ describe GitStore::Commit do
5
+
6
+ REPO = '/tmp/git_store_test'
7
+
8
+ attr_reader :store
9
+
10
+ before(:each) do
11
+ FileUtils.rm_rf REPO
12
+ Dir.mkdir REPO
13
+ Dir.chdir REPO
14
+ `git init`
15
+ @store = GitStore.new(REPO)
16
+ end
17
+
18
+ it "should dump in right format" do
19
+ user = GitStore::User.new("hanni", "hanni@email.de", Time.now)
20
+
21
+ commit = GitStore::Commit.new(nil)
22
+ commit.tree = @store.root.id
23
+ commit.author = user
24
+ commit.committer = user
25
+ commit.message = "This is a message"
26
+
27
+ commit.dump.should == "tree #{@store.root.id}
28
+ author #{user.dump}
29
+ committer #{user.dump}
30
+
31
+ This is a message"
32
+ end
33
+
34
+ it "should be readable by git binary" do
35
+ time = Time.local(2009, 4, 20)
36
+ author = GitStore::User.new("hans", "hans@email.de", time)
37
+
38
+ store['a'] = "Yay"
39
+ commit = store.commit("Commit Message", author, author)
40
+
41
+ IO.popen("git log") do |io|
42
+ io.gets.should == "commit #{commit.id}\n"
43
+ io.gets.should == "Author: hans <hans@email.de>\n"
44
+ io.gets.should == "Date: Mon Apr 20 00:00:00 2009 #{Time.now.strftime('%z')}\n"
45
+ io.gets.should == "\n"
46
+ io.gets.should == " Commit Message\n"
47
+ end
48
+ end
49
+
50
+ it "should diff 2 commits" do
51
+ store['x'] = 'a'
52
+ store['y'] = "
53
+ First Line.
54
+ Second Line.
55
+ Last Line.
56
+ "
57
+ a = store.commit
58
+
59
+ store.delete('x')
60
+ store['y'] = "
61
+ First Line.
62
+ Last Line.
63
+ Another Line.
64
+ "
65
+ store['z'] = 'c'
66
+
67
+ b = store.commit
68
+
69
+ diff = b.diff(a)
70
+
71
+ diff[0].a_path.should == 'x'
72
+ diff[0].deleted_file.should be_true
73
+
74
+ diff[1].a_path.should == 'y'
75
+ diff[1].diff.should == "--- a/y\n+++ b/y\n@@ -1,4 +1,4 @@\n \n First Line.\n-Second Line.\n Last Line.\n+Another Line."
76
+
77
+ diff[2].a_path.should == 'z'
78
+ diff[2].new_file.should be_true
79
+ end
80
+
81
+ end
@@ -0,0 +1,257 @@
1
+ require "#{File.dirname(__FILE__)}/../lib/git_store"
2
+ require "#{File.dirname(__FILE__)}/helper"
3
+ require 'pp'
4
+
5
+ describe GitStore do
6
+
7
+ REPO = '/tmp/git_store_test'
8
+
9
+ attr_reader :store
10
+
11
+ before(:each) do
12
+ FileUtils.rm_rf REPO
13
+ Dir.mkdir REPO
14
+ Dir.chdir REPO
15
+
16
+ `git init`
17
+ @store = GitStore.new(REPO)
18
+ end
19
+
20
+ def file(file, data)
21
+ FileUtils.mkpath(File.dirname(file))
22
+ open(file, 'w') { |io| io << data }
23
+
24
+ `git add #{file}`
25
+ `git commit -m 'added #{file}'`
26
+ File.unlink(file)
27
+ end
28
+
29
+ it 'should fail to initialize without a valid git repository' do
30
+ lambda {
31
+ GitStore.new('/')
32
+ }.should raise_error(ArgumentError)
33
+ end
34
+
35
+ it 'should find modified entries' do
36
+ store['a'] = 'Hello'
37
+
38
+ store.root.should be_modified
39
+
40
+ store.commit
41
+
42
+ store.root.should_not be_modified
43
+
44
+ store['a'] = 'Bello'
45
+
46
+ store.root.should be_modified
47
+ end
48
+
49
+ it 'should load a repo' do
50
+ file 'a', 'Hello'
51
+ file 'b', 'World'
52
+
53
+ store.load
54
+
55
+ store['a'].should == 'Hello'
56
+ store['b'].should == 'World'
57
+ end
58
+
59
+ it 'should load folders' do
60
+ file 'x/a', 'Hello'
61
+ file 'y/b', 'World'
62
+
63
+ store.load
64
+ store['x'].should be_kind_of(GitStore::Tree)
65
+ store['y'].should be_kind_of(GitStore::Tree)
66
+
67
+ store['x']['a'].should == 'Hello'
68
+ store['y']['b'].should == 'World'
69
+ end
70
+
71
+ it 'should load yaml' do
72
+ file 'x/a.yml', '[1, 2, 3, 4]'
73
+
74
+ store.load
75
+
76
+ store['x']['a.yml'].should == [1,2,3,4]
77
+ store['x']['a.yml'] = [1,2,3,4,5]
78
+ end
79
+
80
+ it 'should save yaml' do
81
+ store['x/a.yml'] = [1,2,3,4,5]
82
+ store['x/a.yml'].should == [1,2,3,4,5]
83
+ end
84
+
85
+ it 'should detect modification' do
86
+ store.transaction do
87
+ store['x/a'] = 'a'
88
+ end
89
+
90
+ store.load
91
+
92
+ store['x/a'].should == 'a'
93
+
94
+ store.transaction do
95
+ store['x/a'] = 'b'
96
+ store['x'].should be_modified
97
+ store.root.should be_modified
98
+ end
99
+
100
+ store.load
101
+
102
+ store['x/a'].should == 'b'
103
+ end
104
+
105
+ it 'should resolve paths' do
106
+ file 'x/a', 'Hello'
107
+ file 'y/b', 'World'
108
+
109
+ store.load
110
+
111
+ store['x/a'].should == 'Hello'
112
+ store['y/b'].should == 'World'
113
+
114
+ store['y/b'] = 'Now this'
115
+
116
+ store['y']['b'].should == 'Now this'
117
+ end
118
+
119
+ it 'should create new trees' do
120
+ store['new/tree'] = 'This tree'
121
+ store['new/tree'].should == 'This tree'
122
+ end
123
+
124
+ it 'should delete entries' do
125
+ store['a'] = 'Hello'
126
+ store.delete('a')
127
+
128
+ store['a'].should be_nil
129
+ end
130
+
131
+ it 'should have a head commit' do
132
+ file 'a', 'Hello'
133
+
134
+ store.load
135
+ store.head.should_not be_nil
136
+ end
137
+
138
+ it 'should detect changes' do
139
+ file 'a', 'Hello'
140
+
141
+ store.should be_changed
142
+ end
143
+
144
+ it 'should rollback a transaction' do
145
+ file 'a/b', 'Hello'
146
+ file 'c/d', 'World'
147
+
148
+ begin
149
+ store.transaction do
150
+ store['a/b'] = 'Changed'
151
+ store['x/a'] = 'Added'
152
+ raise
153
+ end
154
+ rescue
155
+ end
156
+
157
+ store['a/b'].should == 'Hello'
158
+ store['c/d'].should == 'World'
159
+ store['x/a'].should be_nil
160
+ end
161
+
162
+ it 'should commit a transaction' do
163
+ file 'a/b', 'Hello'
164
+ file 'c/d', 'World'
165
+
166
+ store.transaction do
167
+ store['a/b'] = 'Changed'
168
+ store['x/a'] = 'Added'
169
+ end
170
+
171
+ a = git_ls_tree(store['a'].id)
172
+ x = git_ls_tree(store['x'].id)
173
+
174
+ a.should == [["100644", "blob", "b653cf27cef08de46da49a11fa5016421e9e3b32", "b"]]
175
+ x.should == [["100644", "blob", "87d2b203800386b1cc8735a7d540a33e246357fa", "a"]]
176
+
177
+ git_show(a[0][2]).should == 'Changed'
178
+ git_show(x[0][2]).should == 'Added'
179
+ end
180
+
181
+ it "should save blobs" do
182
+ store['a'] = 'a'
183
+ store['b'] = 'b'
184
+ store['c'] = 'c'
185
+
186
+ store.commit
187
+
188
+ a = store.id_for('blob', 'a')
189
+ b = store.id_for('blob', 'b')
190
+ c = store.id_for('blob', 'c')
191
+
192
+ git_show(a).should == 'a'
193
+ git_show(b).should == 'b'
194
+ git_show(c).should == 'c'
195
+ end
196
+
197
+ it 'should allow only one transaction' do
198
+ file 'a/b', 'Hello'
199
+
200
+ ready = false
201
+
202
+ store.transaction do
203
+ Thread.start do
204
+ store.transaction do
205
+ store['a/b'] = 'Changed by second thread'
206
+ end
207
+ ready = true
208
+ end
209
+ store['a/b'] = 'Changed'
210
+ end
211
+
212
+ sleep 0.01 until ready
213
+
214
+ store.load
215
+
216
+ store['a/b'].should == 'Changed by second thread'
217
+ end
218
+
219
+ it 'should find all objects' do
220
+ store.load
221
+ store['c'] = 'Hello'
222
+ store['d'] = 'World'
223
+ store.commit
224
+
225
+ store.to_a.should == [['c', 'Hello'], ['d', 'World']]
226
+ end
227
+
228
+ it "should load commits" do
229
+ store['a'] = 'a'
230
+ store.commit 'added a'
231
+
232
+ store['b'] = 'b'
233
+ store.commit 'added b'
234
+
235
+ store.commits[0].message.should == 'added b'
236
+ store.commits[1].message.should == 'added a'
237
+ end
238
+
239
+ it "should load tags" do
240
+ file 'a', 'init'
241
+
242
+ `git tag -m 'message' 0.1`
243
+
244
+ store.load
245
+
246
+ user = GitStore::User.from_config
247
+ id = File.read('.git/refs/tags/0.1')
248
+ tag = store.get(id)
249
+
250
+ tag.type.should == 'commit'
251
+ tag.object.should == store.head
252
+ tag.tagger.name.should == user.name
253
+ tag.tagger.email.should == user.email
254
+ tag.message.should =~ /message/
255
+ end
256
+
257
+ end