git-object-browser 0.0.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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +13 -0
- data/Guardfile +11 -0
- data/LICENSE +22 -0
- data/README.md +18 -0
- data/Rakefile +2 -0
- data/bin/git-object-browser +5 -0
- data/git-object-browser.gemspec +18 -0
- data/htdocs/css/angular-ui.min.css +1 -0
- data/htdocs/css/bootstrap-responsive.css +1058 -0
- data/htdocs/css/bootstrap-responsive.min.css +9 -0
- data/htdocs/css/bootstrap.css +5389 -0
- data/htdocs/css/bootstrap.min.css +699 -0
- data/htdocs/css/font-awesome-ie7.css +645 -0
- data/htdocs/css/font-awesome.css +303 -0
- data/htdocs/css/main.css +22 -0
- data/htdocs/font/fontawesome-webfont.eot +0 -0
- data/htdocs/font/fontawesome-webfont.svg +255 -0
- data/htdocs/font/fontawesome-webfont.ttf +0 -0
- data/htdocs/font/fontawesome-webfont.woff +0 -0
- data/htdocs/img/glyphicons-halflings-white.png +0 -0
- data/htdocs/img/glyphicons-halflings.png +0 -0
- data/htdocs/index.html +114 -0
- data/htdocs/js/main.js +286 -0
- data/htdocs/js/vendor/angular-bootstrap-prettify.min.js +41 -0
- data/htdocs/js/vendor/angular-bootstrap.min.js +9 -0
- data/htdocs/js/vendor/angular-cookies.min.js +7 -0
- data/htdocs/js/vendor/angular-loader.min.js +7 -0
- data/htdocs/js/vendor/angular-resource.min.js +10 -0
- data/htdocs/js/vendor/angular-sanitize.min.js +13 -0
- data/htdocs/js/vendor/angular-ui-ieshiv.min.js +7 -0
- data/htdocs/js/vendor/angular-ui.min.js +7 -0
- data/htdocs/js/vendor/angular.js +14401 -0
- data/htdocs/js/vendor/angular.min.js +158 -0
- data/htdocs/js/vendor/bootstrap.js +2027 -0
- data/htdocs/js/vendor/bootstrap.min.js +6 -0
- data/htdocs/js/vendor/html5shiv.js +5 -0
- data/htdocs/js/vendor/jquery-1.8.2.min.js +2 -0
- data/htdocs/templates/directory.html +25 -0
- data/htdocs/templates/file.html +6 -0
- data/htdocs/templates/git.html +1 -0
- data/htdocs/templates/index.html +44 -0
- data/htdocs/templates/index_entry.html +54 -0
- data/htdocs/templates/info_refs.html +27 -0
- data/htdocs/templates/loading.html +1 -0
- data/htdocs/templates/notfound.html +7 -0
- data/htdocs/templates/object.html +54 -0
- data/htdocs/templates/objects.html +16 -0
- data/htdocs/templates/pack_file.html +15 -0
- data/htdocs/templates/pack_index.html +38 -0
- data/htdocs/templates/packed_object.html +111 -0
- data/htdocs/templates/packed_refs.html +27 -0
- data/htdocs/templates/ref.html +12 -0
- data/lib/git-object-browser.rb +37 -0
- data/lib/git-object-browser/dumper.rb +62 -0
- data/lib/git-object-browser/index_dumper.rb +77 -0
- data/lib/git-object-browser/main.rb +63 -0
- data/lib/git-object-browser/models/bindata.rb +68 -0
- data/lib/git-object-browser/models/directory.rb +61 -0
- data/lib/git-object-browser/models/git_object.rb +127 -0
- data/lib/git-object-browser/models/index.rb +77 -0
- data/lib/git-object-browser/models/index_entry.rb +95 -0
- data/lib/git-object-browser/models/index_reuc_extension.rb +31 -0
- data/lib/git-object-browser/models/index_tree_extension.rb +52 -0
- data/lib/git-object-browser/models/info_refs.rb +32 -0
- data/lib/git-object-browser/models/pack_file.rb +40 -0
- data/lib/git-object-browser/models/pack_index.rb +146 -0
- data/lib/git-object-browser/models/packed_object.rb +239 -0
- data/lib/git-object-browser/models/packed_refs.rb +39 -0
- data/lib/git-object-browser/models/plain_file.rb +24 -0
- data/lib/git-object-browser/models/ref.rb +32 -0
- data/lib/git-object-browser/object_dumper.rb +31 -0
- data/lib/git-object-browser/server/git_servlet.rb +209 -0
- data/lib/git-object-browser/server/main.rb +31 -0
- data/lib/git-object-browser/version.rb +3 -0
- data/spec/fixtures/generate_worktree.sh +103 -0
- data/spec/fixtures/git/indexes/001 +0 -0
- data/spec/fixtures/git/indexes/002-empty-tree-extension +0 -0
- data/spec/fixtures/git/plain_file +1 -0
- data/spec/fixtures/json/blob.json +9 -0
- data/spec/fixtures/json/merge-a.json +48 -0
- data/spec/fixtures/json/test3-tag.json +36 -0
- data/spec/fixtures/json/tree.json +20 -0
- data/spec/fixtures/worktree/_git/COMMIT_EDITMSG +1 -0
- data/spec/fixtures/worktree/_git/HEAD +1 -0
- data/spec/fixtures/worktree/_git/ORIG_HEAD +1 -0
- data/spec/fixtures/worktree/_git/config +11 -0
- data/spec/fixtures/worktree/_git/description +1 -0
- data/spec/fixtures/worktree/_git/hooks/applypatch-msg.sample +15 -0
- data/spec/fixtures/worktree/_git/hooks/commit-msg.sample +24 -0
- data/spec/fixtures/worktree/_git/hooks/post-update.sample +8 -0
- data/spec/fixtures/worktree/_git/hooks/pre-applypatch.sample +14 -0
- data/spec/fixtures/worktree/_git/hooks/pre-commit.sample +50 -0
- data/spec/fixtures/worktree/_git/hooks/pre-rebase.sample +169 -0
- data/spec/fixtures/worktree/_git/hooks/prepare-commit-msg.sample +36 -0
- data/spec/fixtures/worktree/_git/hooks/update.sample +128 -0
- data/spec/fixtures/worktree/_git/ignore +3 -0
- data/spec/fixtures/worktree/_git/index +0 -0
- data/spec/fixtures/worktree/_git/info/exclude +6 -0
- data/spec/fixtures/worktree/_git/info/refs +1 -0
- data/spec/fixtures/worktree/_git/logs/HEAD +10 -0
- data/spec/fixtures/worktree/_git/logs/refs/heads/branch-a +3 -0
- data/spec/fixtures/worktree/_git/logs/refs/heads/branch-b +2 -0
- data/spec/fixtures/worktree/_git/logs/refs/heads/master +4 -0
- data/spec/fixtures/worktree/_git/objects/00/cb8bfeb5b8ce906d39698e4e33b38341f5448f +1 -0
- data/spec/fixtures/worktree/_git/objects/07/31f9d4b6fa0475872be6a8ca263096f1d201cf +2 -0
- data/spec/fixtures/worktree/_git/objects/1d/3dc60b5a117054e43741d51e599ff31bb15f9f +0 -0
- data/spec/fixtures/worktree/_git/objects/26/4e42b1fef5bcb55acec162fdd5a068d79ae551 +0 -0
- data/spec/fixtures/worktree/_git/objects/28/3c06ddf1b31c14bb221d41173299e133b7753d +0 -0
- data/spec/fixtures/worktree/_git/objects/37/d1632d3f1159dad9cfb58e6c34312ab4355c49 +0 -0
- data/spec/fixtures/worktree/_git/objects/3a/2bf444f105c19b13ba5e75e884e10715e95a91 +0 -0
- data/spec/fixtures/worktree/_git/objects/40/a061aaf0cd0555449671a11993e4fed11f91a4 +2 -0
- data/spec/fixtures/worktree/_git/objects/53/2a1874c26cd19bd4d66e03218ab73e63de4357 +3 -0
- data/spec/fixtures/worktree/_git/objects/5b/719b165fde1964fb5a08adaf3b6e4f57ca1ff5 +0 -0
- data/spec/fixtures/worktree/_git/objects/61/cae34206bb889bae43ffdd22c17217485178bf +0 -0
- data/spec/fixtures/worktree/_git/objects/6c/444ac15f1e3c2a6869bd36ca7e58c39512106f +2 -0
- data/spec/fixtures/worktree/_git/objects/93/b714995d24c52180195876058a49c7d7fea0ad +0 -0
- data/spec/fixtures/worktree/_git/objects/96/25401ac3e19ef10868c140a76b719ac3f08fcf +0 -0
- data/spec/fixtures/worktree/_git/objects/b9/29ed2ab14f7489d5238a06d10d2f2c229a4ab4 +0 -0
- data/spec/fixtures/worktree/_git/objects/be/e0d26d33c284ee065e38bd7e81ae4bdc870f89 +4 -0
- data/spec/fixtures/worktree/_git/objects/c3/6491256978d26c08cd7aa97eee0f5631f96659 +0 -0
- data/spec/fixtures/worktree/_git/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 +0 -0
- data/spec/fixtures/worktree/_git/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b +0 -0
- data/spec/fixtures/worktree/_git/objects/e5/b6d4317cefa946d77fc91539f1f1e48b60836f +0 -0
- data/spec/fixtures/worktree/_git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
- data/spec/fixtures/worktree/_git/objects/info/packs +2 -0
- data/spec/fixtures/worktree/_git/objects/pack/pack-f1c1717e9264c12310c7bd3e7dcdd28924000ff6.idx +0 -0
- data/spec/fixtures/worktree/_git/objects/pack/pack-f1c1717e9264c12310c7bd3e7dcdd28924000ff6.pack +0 -0
- data/spec/fixtures/worktree/_git/packed-refs +2 -0
- data/spec/fixtures/worktree/_git/refs/heads/branch-a +1 -0
- data/spec/fixtures/worktree/_git/refs/heads/branch-b +1 -0
- data/spec/fixtures/worktree/_git/refs/heads/master +1 -0
- data/spec/fixtures/worktree/_git/refs/tags/simple-tag +1 -0
- data/spec/fixtures/worktree/_git/refs/tags/test3-tag +1 -0
- data/spec/fixtures/worktree/sample-a.txt +1 -0
- data/spec/fixtures/worktree/sample.txt +1 -0
- data/spec/fixtures/worktree/subdir/sample-sub.txt +0 -0
- data/spec/git-object-browser/main_spec.rb +31 -0
- data/spec/git-object-browser/models/bindata_spec.rb +144 -0
- data/spec/git-object-browser/models/git_object_spec.rb +49 -0
- data/spec/git-object-browser/models/index_spec.rb +42 -0
- data/spec/git-object-browser/models/index_tree_extension_spec.rb +58 -0
- data/spec/git-object-browser/models/plain_file_spec.rb +11 -0
- data/spec/spec_helper.rb +19 -0
- metadata +260 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# https://github.com/git/git/blob/master/sha1_file.c
|
|
4
|
+
# https://github.com/git/git/blob/master/patch-delta.c
|
|
5
|
+
# https://github.com/git/git/blob/master/builtin/unpack-objects.c
|
|
6
|
+
# https://github.com/mojombo/grit/blob/master/lib/grit/git-ruby/internal/pack.rb
|
|
7
|
+
module GitObjectBrowser
|
|
8
|
+
|
|
9
|
+
module Models
|
|
10
|
+
|
|
11
|
+
class PackedObject < Bindata
|
|
12
|
+
|
|
13
|
+
attr_reader :header, :raw_data
|
|
14
|
+
attr_reader :object
|
|
15
|
+
|
|
16
|
+
TYPES = %w{
|
|
17
|
+
undefined
|
|
18
|
+
commit
|
|
19
|
+
tree
|
|
20
|
+
blob
|
|
21
|
+
tag
|
|
22
|
+
undefined
|
|
23
|
+
ofs_delta
|
|
24
|
+
ref_delta
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def initialize(index, input)
|
|
28
|
+
super(input)
|
|
29
|
+
@index = index
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def parse(offset)
|
|
33
|
+
parse_raw(offset)
|
|
34
|
+
input = "#{ @object_type } #{ @object_size }\0" + @raw_data
|
|
35
|
+
@object = GitObject.new(nil).parse_inflated(input)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def parse_raw(offset)
|
|
40
|
+
@offset = offset
|
|
41
|
+
@header = parse_header(offset)
|
|
42
|
+
|
|
43
|
+
if @header[:type] == 'ofs_delta'
|
|
44
|
+
obj_ofs_delta
|
|
45
|
+
elsif @header[:type] == 'ref_delta'
|
|
46
|
+
obj_ref_delta
|
|
47
|
+
else
|
|
48
|
+
@object_type = @header[:type]
|
|
49
|
+
@object_size = @header[:size]
|
|
50
|
+
@raw_data = zlib_inflate
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
[@object_type, @object_size]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def parse_header(offset)
|
|
57
|
+
seek(offset)
|
|
58
|
+
(type, size, header_size) = parse_type_and_size
|
|
59
|
+
type = TYPES[type]
|
|
60
|
+
{ :type => type, :size => size, :header_size => header_size }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def parse_ofs_delta_header(offset, header)
|
|
64
|
+
(delta_offset, delta_header_size) = parse_delta_offset
|
|
65
|
+
header[:base_offset] = offset - delta_offset
|
|
66
|
+
header[:header_size] += delta_header_size
|
|
67
|
+
header
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def parse_ref_delta_header(header)
|
|
71
|
+
header[:base_sha1] = hex(20)
|
|
72
|
+
header[:header_size] += 20
|
|
73
|
+
header
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def to_hash
|
|
77
|
+
return {
|
|
78
|
+
:offset => @offset,
|
|
79
|
+
:type => @header[:type], # commit, tree, blob, tag, ofs_delta, ref_delta
|
|
80
|
+
:size => @header[:size],
|
|
81
|
+
:header_size => @header[:header_size],
|
|
82
|
+
:base_offset => @header[:base_offset],
|
|
83
|
+
:delta_commands => @delta_commands,
|
|
84
|
+
:base_size => @base_size,
|
|
85
|
+
:object => @object.to_hash,
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.path?(relpath)
|
|
90
|
+
return relpath =~ %r{\Aobjects/pack/pack-[0-9a-f]{40}.pack\z}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def obj_ofs_delta
|
|
96
|
+
parse_ofs_delta_header(@offset, @header)
|
|
97
|
+
load_base_and_patch_delta
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def obj_ref_delta
|
|
101
|
+
parse_ref_delta_header(@header)
|
|
102
|
+
index_entry = @index.find(@header[:base_sha1])
|
|
103
|
+
@header[:base_offset] = index_entry[:offset]
|
|
104
|
+
load_base_and_patch_delta
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def load_base_and_patch_delta
|
|
108
|
+
begin
|
|
109
|
+
pack = PackedObject.new(@index, @in)
|
|
110
|
+
(@object_type, _) = pack.parse_raw(@header[:base_offset])
|
|
111
|
+
@base = pack.raw_data
|
|
112
|
+
ensure
|
|
113
|
+
seek(@offset + @header[:header_size])
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
switch_source(StringIO.new(zlib_inflate)) { patch_delta }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def patch_delta
|
|
120
|
+
@base_size = parse_size
|
|
121
|
+
if @base.size != @base_size
|
|
122
|
+
raise 'incollect base size'
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
@object_size = parse_size
|
|
126
|
+
@delta_commands = []
|
|
127
|
+
@raw_data = ''
|
|
128
|
+
while ! @in.eof?
|
|
129
|
+
delta_command
|
|
130
|
+
end
|
|
131
|
+
if @object_size != @raw_data.size
|
|
132
|
+
raise 'incollect delta size'
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def delta_command
|
|
137
|
+
cmd = byte
|
|
138
|
+
data = nil
|
|
139
|
+
if cmd & 0b10000000 != 0
|
|
140
|
+
(offset, size) = parse_base_offset_and_size(cmd)
|
|
141
|
+
data = @base[offset, size]
|
|
142
|
+
@raw_data << data
|
|
143
|
+
@delta_commands << { :source => :base, :offset => offset, :size => size }
|
|
144
|
+
elsif cmd != 0
|
|
145
|
+
size = cmd
|
|
146
|
+
data = raw(size)
|
|
147
|
+
@raw_data << data
|
|
148
|
+
@delta_commands << { :source => :delta, :size => size }
|
|
149
|
+
else
|
|
150
|
+
raise 'delta command is 0'
|
|
151
|
+
end
|
|
152
|
+
@delta_commands.last[:data] = shorten_utf8(data, 2000)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def shorten_utf8(bin, length)
|
|
156
|
+
str = bin.force_encoding('UTF-8')
|
|
157
|
+
str = '(not UTF-8)' unless str.valid_encoding?
|
|
158
|
+
str = str[0, length] + '...' if str.length > length
|
|
159
|
+
str
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def zlib_inflate
|
|
163
|
+
store = Zlib::Inflate.new
|
|
164
|
+
buffer = ''
|
|
165
|
+
while buffer.size < @header[:size]
|
|
166
|
+
rawdata = raw(4096)
|
|
167
|
+
if rawdata.size == 0
|
|
168
|
+
raise 'inflate error'
|
|
169
|
+
end
|
|
170
|
+
buffer << store.inflate(rawdata)
|
|
171
|
+
end
|
|
172
|
+
store.close
|
|
173
|
+
buffer
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# sha1_file.c unpack_object_header_buffer
|
|
177
|
+
# unpack-objects.c unpack_one
|
|
178
|
+
def parse_type_and_size
|
|
179
|
+
hdr = byte
|
|
180
|
+
hdr_size = 1
|
|
181
|
+
continue = (hdr & 0b10000000)
|
|
182
|
+
type = (hdr & 0b01110000) >> 4
|
|
183
|
+
size = (hdr & 0b00001111)
|
|
184
|
+
size_len = 4
|
|
185
|
+
while continue != 0
|
|
186
|
+
hdr = byte
|
|
187
|
+
hdr_size += 1
|
|
188
|
+
continue = (hdr & 0b10000000)
|
|
189
|
+
size += (hdr & 0b01111111) << size_len
|
|
190
|
+
size_len += 7
|
|
191
|
+
end
|
|
192
|
+
return [type, size, hdr_size]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# delta.h get_delta_hdr_size
|
|
196
|
+
def parse_size
|
|
197
|
+
size = 0
|
|
198
|
+
size_len = 0
|
|
199
|
+
begin
|
|
200
|
+
hdr = byte
|
|
201
|
+
continue = (hdr & 0b10000000)
|
|
202
|
+
size += (hdr & 0b01111111) << size_len
|
|
203
|
+
size_len += 7
|
|
204
|
+
end while continue != 0
|
|
205
|
+
return size
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# sha1_file.c get_delta_base
|
|
209
|
+
# unpack-objects.c unpack_delta_entry
|
|
210
|
+
def parse_delta_offset
|
|
211
|
+
offset = -1
|
|
212
|
+
hdr_size = 0
|
|
213
|
+
begin
|
|
214
|
+
hdr = byte
|
|
215
|
+
hdr_size += 1
|
|
216
|
+
continue = hdr & 0b10000000
|
|
217
|
+
low_offset = hdr & 0b01111111
|
|
218
|
+
offset = ((offset + 1) << 7) | low_offset
|
|
219
|
+
end while continue != 0
|
|
220
|
+
return [offset, hdr_size]
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# patch-delta.c
|
|
224
|
+
def parse_base_offset_and_size(cmd)
|
|
225
|
+
offset = size = 0
|
|
226
|
+
offset = byte if cmd & 0b00000001 != 0
|
|
227
|
+
offset |= byte << 8 if cmd & 0b00000010 != 0
|
|
228
|
+
offset |= byte << 16 if cmd & 0b00000100 != 0
|
|
229
|
+
offset |= byte << 24 if cmd & 0b00001000 != 0
|
|
230
|
+
size = byte if cmd & 0b00010000 != 0
|
|
231
|
+
size |= byte << 8 if cmd & 0b00100000 != 0
|
|
232
|
+
size |= byte << 16 if cmd & 0b01000000 != 0
|
|
233
|
+
size = 0x10000 if size == 0
|
|
234
|
+
return [offset, size]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
module GitObjectBrowser
|
|
3
|
+
|
|
4
|
+
module Models
|
|
5
|
+
|
|
6
|
+
class PackedRefs
|
|
7
|
+
def initialize(input)
|
|
8
|
+
@entries = []
|
|
9
|
+
while (line = input.gets) do
|
|
10
|
+
next if line =~ /\A\s*#/
|
|
11
|
+
next unless line =~ /(\^)?([0-9a-f]{40})\s*(.*)/
|
|
12
|
+
sha1 = $2
|
|
13
|
+
ref = $3
|
|
14
|
+
if $1
|
|
15
|
+
entry = @entries.last
|
|
16
|
+
entry[:tag_sha1] = sha1 if entry
|
|
17
|
+
else
|
|
18
|
+
entry = {}
|
|
19
|
+
entry[:sha1] = sha1
|
|
20
|
+
entry[:ref] = ref
|
|
21
|
+
@entries << entry
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_hash
|
|
27
|
+
return {
|
|
28
|
+
:entries => @entries
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.path?(relpath)
|
|
33
|
+
return relpath == "packed-refs"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
module GitObjectBrowser
|
|
3
|
+
|
|
4
|
+
module Models
|
|
5
|
+
|
|
6
|
+
class PlainFile
|
|
7
|
+
def initialize(input)
|
|
8
|
+
@in = input
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def parse
|
|
12
|
+
@content = @in.read(nil)
|
|
13
|
+
@content = @content.force_encoding('UTF-8')
|
|
14
|
+
@content = '(not UTF-8)' unless @content.valid_encoding?
|
|
15
|
+
@content = @content[0, 3000] + "\n..." if @content.length > 3000
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_hash
|
|
20
|
+
return { 'content' => @content }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
module GitObjectBrowser
|
|
3
|
+
|
|
4
|
+
module Models
|
|
5
|
+
|
|
6
|
+
class Ref
|
|
7
|
+
def initialize(input)
|
|
8
|
+
line = input.gets.to_s
|
|
9
|
+
|
|
10
|
+
if line =~ %r{\Aref:\s*(.+)}
|
|
11
|
+
@ref = $1
|
|
12
|
+
elsif line =~ %r{\A([0-9a-f]{40})}
|
|
13
|
+
@sha1 = $1
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_hash
|
|
18
|
+
return {
|
|
19
|
+
"ref" => @ref,
|
|
20
|
+
"sha1" => @sha1
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.path?(relpath)
|
|
25
|
+
ref_names = %w{HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD CHERRY_PICK_HEAD}
|
|
26
|
+
return (ref_names.include?(relpath) || relpath =~ %r{\Arefs/})
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
module GitObjectBrowser
|
|
4
|
+
|
|
5
|
+
class ObjectDumper
|
|
6
|
+
|
|
7
|
+
def initialize(input, output)
|
|
8
|
+
@object = Models::GitObject.new(input)
|
|
9
|
+
@out = output
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def dump
|
|
13
|
+
@out << "type: #{@object.type}\n"
|
|
14
|
+
@out << "size: #{@object.size}\n"
|
|
15
|
+
@out << "sha1: #{@object.sha1}\n"
|
|
16
|
+
@out << "\n"
|
|
17
|
+
if @object.type == "tree"
|
|
18
|
+
dump_tree_entries
|
|
19
|
+
else
|
|
20
|
+
@out << @object.contents
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# man git-ls-tree
|
|
25
|
+
def dump_tree_entries
|
|
26
|
+
@object.entries.each do |entry|
|
|
27
|
+
@out << "#{entry[:mode]} #{entry[:sha1]} #{entry[:filename]}\n"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
|
|
2
|
+
module GitObjectBrowser
|
|
3
|
+
|
|
4
|
+
module Server
|
|
5
|
+
|
|
6
|
+
class GitServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
7
|
+
def initialize(server, target)
|
|
8
|
+
@target = File.expand_path(target)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def do_GET(request, response)
|
|
12
|
+
# status, content_type, body = do_stuff_with request
|
|
13
|
+
path = request.path
|
|
14
|
+
unless path =~ %r{/.git(?:/(.*))?}
|
|
15
|
+
not_found(response)
|
|
16
|
+
return
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@relpath = $1.to_s
|
|
20
|
+
path = File.join(@target, @relpath)
|
|
21
|
+
unless File.exist?(path)
|
|
22
|
+
not_found(response) unless redirect_to_packed_object(response)
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if path =~ /\.\./
|
|
27
|
+
not_found(response)
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
return if response_directory(response)
|
|
32
|
+
return if response_index(response)
|
|
33
|
+
return if response_object(response)
|
|
34
|
+
return if response_ref(response)
|
|
35
|
+
return if response_packed_object(response, request.query["offset"])
|
|
36
|
+
return if response_pack_file(response)
|
|
37
|
+
return if response_pack_index(response)
|
|
38
|
+
return if response_info_refs(response)
|
|
39
|
+
return if response_packed_refs(response)
|
|
40
|
+
|
|
41
|
+
response_file(response)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def redirect_to_packed_object(response)
|
|
45
|
+
return false unless GitObjectBrowser::Models::GitObject.path?(@relpath)
|
|
46
|
+
sha1 = @relpath.gsub(%r{\A.*/([0-9a-f]{2})/([0-9a-f]{38})\z}, '\1\2')
|
|
47
|
+
|
|
48
|
+
Dir.chdir(@target) do
|
|
49
|
+
Dir.glob('objects/pack/*.idx') do |path|
|
|
50
|
+
File.open(path) do |input|
|
|
51
|
+
index = GitObjectBrowser::Models::PackIndex.new(input)
|
|
52
|
+
result = index.find(sha1)
|
|
53
|
+
unless result.nil?
|
|
54
|
+
packfile = path.sub(/\.idx\z/, '.pack')
|
|
55
|
+
response.status = 302
|
|
56
|
+
response["Location"] = "/.git/#{ packfile }?offset=#{ result[:offset] }"
|
|
57
|
+
return true
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def response_wrapped_object(response, type, obj)
|
|
66
|
+
ok(response)
|
|
67
|
+
hash = {}
|
|
68
|
+
hash["type"] = type
|
|
69
|
+
hash["object"] = obj.to_hash
|
|
70
|
+
hash["root"] = @target
|
|
71
|
+
hash["path"] = @relpath
|
|
72
|
+
hash["wroking_dir"] = File.basename(File.dirname(@target))
|
|
73
|
+
|
|
74
|
+
response.body = ::JSON.generate(hash)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def response_directory(response)
|
|
78
|
+
return false unless File.directory?(File.join(@target, @relpath))
|
|
79
|
+
|
|
80
|
+
obj = GitObjectBrowser::Models::Directory.new(@target, @relpath)
|
|
81
|
+
response_wrapped_object(response, "directory", obj)
|
|
82
|
+
return true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def response_index(response)
|
|
86
|
+
return false unless @relpath == "index"
|
|
87
|
+
|
|
88
|
+
obj = {}
|
|
89
|
+
File.open(File.join(@target, @relpath)) do |input|
|
|
90
|
+
obj = GitObjectBrowser::Models::Index.new(input).parse
|
|
91
|
+
end
|
|
92
|
+
response_wrapped_object(response, "index", obj)
|
|
93
|
+
return true
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def response_object(response)
|
|
97
|
+
return false unless GitObjectBrowser::Models::GitObject.path?(@relpath)
|
|
98
|
+
|
|
99
|
+
obj = {}
|
|
100
|
+
File.open(File.join(@target, @relpath)) do |input|
|
|
101
|
+
obj = GitObjectBrowser::Models::GitObject.new(input).parse
|
|
102
|
+
end
|
|
103
|
+
response_wrapped_object(response, "object", obj)
|
|
104
|
+
return true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def response_ref(response)
|
|
108
|
+
return false unless GitObjectBrowser::Models::Ref.path?(@relpath)
|
|
109
|
+
|
|
110
|
+
obj = {}
|
|
111
|
+
File.open(File.join(@target, @relpath)) do |input|
|
|
112
|
+
obj = GitObjectBrowser::Models::Ref.new(input)
|
|
113
|
+
end
|
|
114
|
+
response_wrapped_object(response, "ref", obj)
|
|
115
|
+
return true
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def response_pack_index(response)
|
|
119
|
+
return false unless GitObjectBrowser::Models::PackIndex.path?(@relpath)
|
|
120
|
+
obj = {}
|
|
121
|
+
File.open(File.join(@target, @relpath)) do |input|
|
|
122
|
+
obj = GitObjectBrowser::Models::PackIndex.new(input).parse
|
|
123
|
+
end
|
|
124
|
+
File.open(index_to_pack_path) do |input|
|
|
125
|
+
obj.load_object_types(input)
|
|
126
|
+
end
|
|
127
|
+
response_wrapped_object(response, "pack_index", obj)
|
|
128
|
+
return true
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def response_pack_file(response)
|
|
132
|
+
return false unless GitObjectBrowser::Models::PackFile.path?(@relpath)
|
|
133
|
+
obj = {}
|
|
134
|
+
File.open(File.join(@target, @relpath)) do |input|
|
|
135
|
+
obj = GitObjectBrowser::Models::PackFile.new(input).parse
|
|
136
|
+
end
|
|
137
|
+
response_wrapped_object(response, 'pack_file', obj)
|
|
138
|
+
return true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def index_to_pack_path
|
|
142
|
+
File.join(@target, @relpath.sub(/\.idx\z/, '.pack'))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def pack_to_index_path
|
|
146
|
+
File.join(@target, @relpath.sub(/\.pack\z/, '.idx'))
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def response_packed_object(response, offset)
|
|
150
|
+
return false if offset.nil?
|
|
151
|
+
return false unless GitObjectBrowser::Models::PackedObject.path?(@relpath)
|
|
152
|
+
obj = {}
|
|
153
|
+
|
|
154
|
+
File.open(pack_to_index_path) do |index_input|
|
|
155
|
+
index = GitObjectBrowser::Models::PackIndex.new(index_input)
|
|
156
|
+
File.open(File.join(@target, @relpath)) do |input|
|
|
157
|
+
obj = GitObjectBrowser::Models::PackedObject.new(index, input).parse(offset.to_i)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
response_wrapped_object(response, "packed_object", obj)
|
|
161
|
+
return true
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def response_info_refs(response)
|
|
165
|
+
return false unless GitObjectBrowser::Models::InfoRefs.path?(@relpath)
|
|
166
|
+
|
|
167
|
+
obj = {}
|
|
168
|
+
File.open(File.join(@target, @relpath)) do |input|
|
|
169
|
+
obj = GitObjectBrowser::Models::InfoRefs.new(input)
|
|
170
|
+
end
|
|
171
|
+
response_wrapped_object(response, "info_refs", obj)
|
|
172
|
+
return true
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def response_packed_refs(response)
|
|
176
|
+
return false unless GitObjectBrowser::Models::PackedRefs.path?(@relpath)
|
|
177
|
+
|
|
178
|
+
obj = {}
|
|
179
|
+
File.open(File.join(@target, @relpath)) do |input|
|
|
180
|
+
obj = GitObjectBrowser::Models::PackedRefs.new(input)
|
|
181
|
+
end
|
|
182
|
+
response_wrapped_object(response, "packed_refs", obj)
|
|
183
|
+
return true
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def response_file(response)
|
|
187
|
+
path = File.join(@target, @relpath)
|
|
188
|
+
obj = {}
|
|
189
|
+
File.open(path) do |input|
|
|
190
|
+
obj = GitObjectBrowser::Models::PlainFile.new(input).parse
|
|
191
|
+
end
|
|
192
|
+
response_wrapped_object(response, "file", obj)
|
|
193
|
+
return true
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def ok(response)
|
|
197
|
+
response.status = 200
|
|
198
|
+
response['Content-Type'] = 'application/json'
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def not_found(response)
|
|
202
|
+
response.status = 404
|
|
203
|
+
response['Content-Type'] = 'application/json'
|
|
204
|
+
response.body = '{}'
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|