git-db 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ class GitDB::Objects::Commit < GitDB::Objects::Base
2
+
3
+ def raw
4
+ "commit #{data.length}\000#{data}"
5
+ end
6
+
7
+ def type
8
+ GitDB::OBJ_COMMIT
9
+ end
10
+
11
+ def message
12
+ data.split("\n\n", 2).last
13
+ end
14
+
15
+ def author
16
+ attributes['author'].first
17
+ end
18
+
19
+ def committer
20
+ attributes['committer'].first
21
+ end
22
+
23
+ def tree
24
+ attributes['tree'].first
25
+ end
26
+
27
+ def parents
28
+ attributes['parent']
29
+ end
30
+
31
+ def properties
32
+ [:tree, :parents, :author, :committer, :message]
33
+ end
34
+
35
+ private ######################################################################
36
+
37
+ def attributes
38
+ @attributes ||= begin
39
+ attributes = data.split("\n\n", 2).first
40
+ attributes.split("\n").inject({}) do |hash, line|
41
+ key, value = line.split(' ', 2)
42
+ hash[key] ||= []
43
+ hash[key] << value
44
+ hash
45
+ end
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,27 @@
1
+ require 'stringio'
2
+
3
+ class GitDB::Objects::Entry < GitDB::Objects::Base
4
+
5
+ attr_reader :sha, :permissions, :name
6
+
7
+ def initialize(sha, permissions, name)
8
+ @sha = sha
9
+ @permissions = permissions
10
+ @name = name
11
+ end
12
+
13
+ def properties
14
+ [:permissions, :name]
15
+ end
16
+
17
+ def to_hash
18
+ { :sha => sha, :permissions => permissions, :name => name }
19
+ end
20
+
21
+ def to_json
22
+ to_hash.to_json
23
+ end
24
+
25
+ private ######################################################################
26
+
27
+ end
@@ -0,0 +1,7 @@
1
+ class GitDB::Objects::Tag < GitDB::Objects::Base
2
+
3
+ def type
4
+ GitDB::OBJ_TAG
5
+ end
6
+
7
+ end
@@ -0,0 +1,45 @@
1
+ require 'stringio'
2
+
3
+ class GitDB::Objects::Tree < GitDB::Objects::Base
4
+
5
+ # TODO: memoize entries
6
+
7
+ def entries
8
+ entries = []
9
+ stream = StringIO.new(data)
10
+ until stream.eof?
11
+ perms = read_until(stream, ' ').to_i
12
+ name = read_until(stream, 0.chr)
13
+ sha = GitDB.sha1_to_hex(stream.read(20))
14
+ entries << GitDB::Objects::Entry.new(sha, perms, name)
15
+ end
16
+ entries
17
+ end
18
+
19
+ def properties
20
+ [:entries]
21
+ end
22
+
23
+ def raw
24
+ "tree #{data.length}\000#{data}"
25
+ end
26
+
27
+ def type
28
+ GitDB::OBJ_TREE
29
+ end
30
+
31
+ private ######################################################################
32
+
33
+ def read_until(stream, separator)
34
+ data = ""
35
+ char = ""
36
+ loop do
37
+ char = stream.read(1)
38
+ break if char.nil?
39
+ break if char == separator
40
+ data << char
41
+ end
42
+ data
43
+ end
44
+
45
+ end
@@ -0,0 +1,20 @@
1
+ module GitDB::Objects;
2
+
3
+ def self.new_from_type(type, data)
4
+ case type
5
+ when GitDB::OBJ_COMMIT then GitDB::Objects::Commit.new(data)
6
+ when GitDB::OBJ_TREE then GitDB::Objects::Tree.new(data)
7
+ when GitDB::OBJ_BLOB then GitDB::Objects::Blob.new(data)
8
+ when GitDB::OBJ_TAG then GitDB::Objects::Tag.new(data)
9
+ else raise "Unknown object type: #{type}"
10
+ end
11
+ end
12
+
13
+ end
14
+
15
+ require 'git-db/objects/base'
16
+ require 'git-db/objects/blob'
17
+ require 'git-db/objects/commit'
18
+ require 'git-db/objects/entry'
19
+ require 'git-db/objects/tag'
20
+ require 'git-db/objects/tree'
@@ -0,0 +1,182 @@
1
+ require 'digest/sha1'
2
+ require 'stringio'
3
+ require 'zlib'
4
+
5
+ class GitDB::Pack
6
+
7
+ PackObject = Struct.new(:type, :offset, :data)
8
+
9
+ attr_reader :io
10
+
11
+ def initialize(io)
12
+ @io = GitDB::Utility::CountingIO.new(io)
13
+ end
14
+
15
+ def read
16
+ header = io.read(12)
17
+ return nil unless header
18
+
19
+ signature, version, entries = header.unpack("a4NN")
20
+ raise 'invalid pack signature' unless signature == 'PACK'
21
+ raise 'invalid version' unless version == 2
22
+
23
+ objects = {}
24
+
25
+ 1.upto(entries) do
26
+ object_offset = io.offset
27
+
28
+ type, size = unpack_pack_header(io)
29
+
30
+ object = case type
31
+ when 1 then
32
+ GitDB::Objects::Commit.new(read_compressed(io))
33
+ when 2 then
34
+ GitDB::Objects::Tree.new(read_compressed(io))
35
+ when 3 then
36
+ GitDB::Objects::Blob.new(read_compressed(io))
37
+ when 4 then
38
+ GitDB::Objects::Tag.new(read_compressed(io))
39
+ when 5 then
40
+ raise 'Invalid Type: 5'
41
+ when 6 then
42
+ offset = object_offset - unpack_delta_size(io)
43
+ patch = read_compressed(io)
44
+ base = objects[offset]
45
+ base.class.new(apply_patch(base.data, patch))
46
+ when 7 then
47
+ # TODO
48
+ sha = io.read(20)
49
+ # base = lookup_by_sha(sha)
50
+ patch = read_compressed(io)
51
+ # base.class.new(apply_patch(base.data, patch))
52
+ nil
53
+ end
54
+
55
+ objects[object_offset] = object
56
+ end
57
+
58
+ GitDB.log(objects.values.map { |o| o.inspect })
59
+
60
+ io.read(20)
61
+
62
+ objects.values.compact
63
+ end
64
+
65
+ def write(entries)
66
+ buffer = ""
67
+ signature = ["PACK", 2, entries.length].pack("a4NN")
68
+ #GitDB.log("SIGNATURE: #{signature}")
69
+ io.write(signature)
70
+ buffer << signature
71
+
72
+ entries.each do |entry|
73
+ header = pack_pack_header(entry.type, entry.data.length)
74
+ #GitDB.log("HEADER: #{header.inspect}")
75
+ io.write(header)
76
+ buffer << header
77
+ compressed = Zlib::Deflate.deflate(entry.data)
78
+ io.write(compressed)
79
+ buffer << compressed
80
+ end
81
+
82
+ #GitDB.log("BUFFER: #{buffer.inspect}")
83
+ signature = GitDB::hex_to_sha1(Digest::SHA1.hexdigest(buffer))
84
+ #GitDB.log("SIGNATURE: #{signature.inspect}")
85
+ io.write(signature)
86
+ io.flush
87
+ end
88
+
89
+ private ######################################################################
90
+
91
+ def apply_patch(original, patch)
92
+ patch_stream = StringIO.new(patch)
93
+ source_size = unpack_size(patch_stream)
94
+ destination_size = unpack_size(patch_stream)
95
+
96
+ data = ""
97
+
98
+ until patch_stream.eof?
99
+ offset = size = 0
100
+ cmd = patch_stream.read(1)[0]
101
+ if (cmd & 0x80) != 0
102
+ offset = (patch_stream.read(1)[0]) if (cmd & 0x01) != 0
103
+ offset |= (patch_stream.read(1)[0] << 8) if (cmd & 0x02) != 0
104
+ offset |= (patch_stream.read(1)[0] << 16) if (cmd & 0x04) != 0
105
+ offset |= (patch_stream.read(1)[0] << 24) if (cmd & 0x08) != 0
106
+ size = (patch_stream.read(1)[0]) if (cmd & 0x10) != 0
107
+ size |= (patch_stream.read(1)[0] << 8) if (cmd & 0x20) != 0
108
+ size |= (patch_stream.read(1)[0] << 16) if (cmd & 0x40) != 0
109
+ size = 0x10000 if size == 0
110
+
111
+ if ((offset + size) < size) ||
112
+ ((offset + size) > source_size) ||
113
+ (size > destination_size)
114
+ break
115
+ end
116
+ data += original[offset,size]
117
+ elsif (cmd != 0)
118
+ data += patch_stream.read(cmd)
119
+ end
120
+ end
121
+
122
+ data
123
+ end
124
+
125
+ def read_compressed(stream)
126
+ zstream = Zlib::Inflate.new
127
+ data = ""
128
+ loop do
129
+ data += zstream.inflate(stream.read(1))
130
+ break if zstream.finished?
131
+ end
132
+ data
133
+ end
134
+
135
+ def pack_pack_header(type, size)
136
+ data = ""
137
+ c = (type << 4) | (size & 15);
138
+ size >>= 4;
139
+ while (size > 0)
140
+ data << (c | 0x80).chr
141
+ c = size & 0x7f;
142
+ size >>= 7;
143
+ end
144
+ data << c.chr
145
+ end
146
+
147
+ def unpack_delta_size(stream)
148
+ c = stream.read(1)[0]
149
+ size = (c & 127)
150
+ while (c & 128) != 0
151
+ size += 1
152
+ c = stream.read(1)[0]
153
+ size = (size << 7) + (c & 127)
154
+ end
155
+ size
156
+ end
157
+
158
+ def unpack_pack_header(stream)
159
+ c = stream.read(1)[0]
160
+ type = (c >> 4) & 7
161
+ size = (c & 15)
162
+ shift = 4
163
+ while ((c & 0x80) != 0)
164
+ c = stream.read(1)[0]
165
+ size += ((c & 0x7f) << shift)
166
+ shift += 7
167
+ end
168
+ [type, size]
169
+ end
170
+
171
+ def unpack_size(stream)
172
+ size = shift = 0
173
+ loop do
174
+ c = stream.read(1)[0]
175
+ size += (c & 127) << shift
176
+ shift += 7
177
+ break if (c & 128) == 0
178
+ end
179
+ size
180
+ end
181
+
182
+ end
@@ -0,0 +1,73 @@
1
+ class GitDB::Protocol
2
+
3
+ attr_reader :reader
4
+ attr_reader :writer
5
+
6
+ def initialize(io=nil)
7
+ if io
8
+ @reader = io
9
+ @writer = io
10
+ else
11
+ @reader = STDIN
12
+ @writer = STDOUT
13
+ end
14
+ end
15
+
16
+ ## commands ##################################################################
17
+
18
+ def flush
19
+ writer.flush
20
+ end
21
+
22
+ def read_command
23
+ length = reader.read(4)
24
+ return nil unless length
25
+ length = length.to_i(16) - 4
26
+ if (length == -4)
27
+ GitDB.log('GOT EOF')
28
+ return
29
+ end
30
+ data = reader.read(length)
31
+ GitDB.log("GOT DATA: #{data.inspect}")
32
+ data
33
+ end
34
+
35
+ def write_command(command)
36
+ raw_command = encode_command(command)
37
+ GitDB.log("WWRITING COMMAND: #{raw_command.inspect}")
38
+ writer.print raw_command
39
+ writer.flush
40
+ end
41
+
42
+ def write(data)
43
+ writer.write data
44
+ writer.flush
45
+ end
46
+
47
+ def write_eof
48
+ #GitDB.log("WRITING EOF")
49
+ writer.print '0000'
50
+ writer.flush
51
+ end
52
+
53
+ ## packs #####################################################################
54
+
55
+ def read_pack
56
+ GitDB::Pack.new(reader).read
57
+ end
58
+
59
+ def write_pack(entries)
60
+ GitDB::Pack.new(writer).write(entries)
61
+ end
62
+
63
+ private ######################################################################
64
+
65
+ def encode_command(command)
66
+ length_as_hex(command) << command
67
+ end
68
+
69
+ def length_as_hex(command)
70
+ hex = (command.length + 4).to_s(16).rjust(4, '0')
71
+ end
72
+
73
+ end
@@ -0,0 +1,24 @@
1
+ class GitDB::Utility::CountingIO
2
+
3
+ attr_reader :io, :offset
4
+
5
+ def initialize(io)
6
+ @io = io
7
+ @offset = 0
8
+ end
9
+
10
+ def flush
11
+ io.flush
12
+ end
13
+
14
+ def read(n)
15
+ data = io.read(n)
16
+ @offset += n
17
+ data
18
+ end
19
+
20
+ def write(data)
21
+ io.write(data)
22
+ end
23
+
24
+ end
@@ -0,0 +1,3 @@
1
+ module GitDB::Utility; end
2
+
3
+ require 'git-db/utility/counting_io'
data/lib/git-db.rb ADDED
@@ -0,0 +1,68 @@
1
+ require 'base64'
2
+ require 'couchrest'
3
+ require 'logger'
4
+
5
+ module GitDB;
6
+
7
+ ## git constants #############################################################
8
+
9
+ OBJ_NONE = 0
10
+ OBJ_COMMIT = 1
11
+ OBJ_TREE = 2
12
+ OBJ_BLOB = 3
13
+ OBJ_TAG = 4
14
+ OBJ_OFS_DELTA = 6
15
+ OBJ_REF_DELTA = 7
16
+
17
+ ## git utility ###############################################################
18
+
19
+ def self.sha1_to_hex(sha)
20
+ hex = ""
21
+ sha.split('').each do |char|
22
+ val = char[0]
23
+ hex << (val >> 4).to_s(16)
24
+ hex << (val & 0xf).to_s(16)
25
+ end
26
+ hex
27
+ end
28
+
29
+ def self.hex_to_sha1(hex)
30
+ sha = ""
31
+ len = 0
32
+ until (len == hex.length)
33
+ val = (hex[len, 1].to_i(16) << 4)
34
+ val += hex[len+1, 1].to_i(16)
35
+ sha << val.chr
36
+ len += 2
37
+ end
38
+ sha
39
+ end
40
+
41
+ def self.null_sha1
42
+ "0000000000000000000000000000000000000000"
43
+ end
44
+
45
+ ## logging ###################################################################
46
+
47
+ def self.logger
48
+ @logger ||= STDERR
49
+ end
50
+
51
+ def self.log(message)
52
+ logger.puts message
53
+ end
54
+
55
+ ## database ##################################################################
56
+
57
+ def self.database(repository)
58
+ GitDB::Database.database(repository)
59
+ end
60
+
61
+ end
62
+
63
+ require 'git-db/commands'
64
+ require 'git-db/database'
65
+ require 'git-db/objects'
66
+ require 'git-db/pack'
67
+ require 'git-db/protocol'
68
+ require 'git-db/utility'
@@ -0,0 +1,23 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "GitDB::Commands" do
4
+
5
+ it "has a receive-pack command" do
6
+ GitDB::Commands.commands['receive-pack'].should_not be_nil
7
+ end
8
+
9
+ it "has an upload-pack command" do
10
+ end
11
+
12
+ it "executes a command" do
13
+ @command = mock
14
+ @name = 'command'
15
+ @args = ['Test Repository']
16
+
17
+ GitDB::Commands.should_receive(:commands).at_least(:once).and_return({ @name => @command })
18
+ @command.should_receive(:execute).with(@args)
19
+
20
+ GitDB::Commands.execute(@name, @args)
21
+ end
22
+
23
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "GitDB::Objects::Base" do
4
+
5
+ before(:each) do
6
+ @data = "Test Data"
7
+ @base = GitDB::Objects::Base.new(@data)
8
+ end
9
+
10
+ it "initializes with data" do
11
+ @base.data.should == @data
12
+ end
13
+
14
+ it "inspects its own properties" do
15
+ @base.should_receive(:properties).and_return([:data])
16
+ @base.inspect
17
+ end
18
+
19
+ it "has default properties" do
20
+ @base.properties.should == [:data]
21
+ end
22
+
23
+ it "has default raw" do
24
+ @base.raw.should == @data
25
+ end
26
+
27
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "GitDB::Objects::Entry" do
4
+
5
+ before(:each) do
6
+ @sha = "1111111111111111111111111111111111111111"
7
+ @perms = 100644
8
+ @name = "test name"
9
+ @entry = GitDB::Objects::Entry.new(@sha, @perms, @name)
10
+ end
11
+
12
+ it "has properties" do
13
+ @entry.properties.should == [:permissions, :name]
14
+ end
15
+
16
+ it "converts to a hash" do
17
+ @entry.to_hash[:sha].should == @sha
18
+ @entry.to_hash[:permissions].should == @perms
19
+ @entry.to_hash[:name].should == @name
20
+ end
21
+
22
+ it "converts to json" do
23
+ @entry.to_json.should == @entry.to_hash.to_json
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "GitDB::Objects::Tag" do
4
+
5
+ before(:each) do
6
+ # TODO: real tag data
7
+ @data = "test-tag"
8
+ @tag = GitDB::Objects::Tag.new(@data)
9
+ end
10
+
11
+ it "has a type" do
12
+ @tag.type.should == GitDB::OBJ_TAG
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "GitDB::Objects::Tree" do
4
+
5
+ class TreeEntry
6
+ attr_reader :perms, :name, :sha
7
+
8
+ def initialize(perms, name, sha)
9
+ @perms = perms
10
+ @name = name
11
+ @sha = sha
12
+ end
13
+
14
+ def to_entry
15
+ "#{perms} #{name}\000#{GitDB.hex_to_sha1(sha)}"
16
+ end
17
+ end
18
+
19
+ before(:each) do
20
+ @entry1 = TreeEntry.new(100644, "entry1", "1111111111111111111111111111111111111111")
21
+ @entry2 = TreeEntry.new(100444, "entry2", "2222222222222222222222222222222222222222")
22
+ @data = [ @entry1.to_entry, @entry2.to_entry ].join('')
23
+ @tree = GitDB::Objects::Tree.new(@data)
24
+ end
25
+
26
+ it "has entries" do
27
+ @tree.entries.first.name.should == @entry1.name
28
+ @tree.entries.last.name.should == @entry2.name
29
+ end
30
+
31
+ it "has properties" do
32
+ @tree.properties.should == [:entries]
33
+ end
34
+
35
+ it "has a raw value" do
36
+ @tree.raw.should == "tree #{@data.length}\000#{@data}"
37
+ end
38
+
39
+ it "has a type" do
40
+ @tree.type.should == GitDB::OBJ_TREE
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "GitDB::Utility::CountingIO" do
4
+
5
+ before(:each) do
6
+ @base = StringIO.new
7
+ @io = GitDB::Utility::CountingIO.new(@base)
8
+ end
9
+
10
+ it "can flush" do
11
+ @base.should_receive(:flush)
12
+ @io.flush
13
+ end
14
+
15
+ it "can read" do
16
+ @data = "00000"
17
+ @bytes = @data.length
18
+
19
+ @base.should_receive(:read).with(@bytes).and_return(@data)
20
+ @io.read(@bytes).should == @data
21
+ end
22
+
23
+ it "can write" do
24
+ @data = "00000"
25
+
26
+ @base.should_receive(:write).with(@data)
27
+ @io.write(@data)
28
+ end
29
+
30
+ end