gitki 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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