git-db 0.1.2

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.
@@ -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