repobrowse 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitattributes +5 -0
- data/.gitignore +3 -0
- data/COPYING +661 -0
- data/GNUmakefile +38 -0
- data/MANIFEST +42 -0
- data/README +35 -0
- data/lib/repobrowse.rb +7 -0
- data/lib/repobrowse/app.rb +60 -0
- data/lib/repobrowse/config.rb +66 -0
- data/lib/repobrowse/error.rb +21 -0
- data/lib/repobrowse/escape.rb +28 -0
- data/lib/repobrowse/git.rb +71 -0
- data/lib/repobrowse/git_atom.rb +109 -0
- data/lib/repobrowse/git_commit_html.rb +320 -0
- data/lib/repobrowse/git_disambiguate.rb +21 -0
- data/lib/repobrowse/git_http_backend.rb +109 -0
- data/lib/repobrowse/git_log.rb +4 -0
- data/lib/repobrowse/git_patch.rb +55 -0
- data/lib/repobrowse/git_raw.rb +50 -0
- data/lib/repobrowse/git_raw_tree_html.rb +50 -0
- data/lib/repobrowse/git_show.rb +32 -0
- data/lib/repobrowse/git_src.rb +37 -0
- data/lib/repobrowse/git_src_blob_html.rb +89 -0
- data/lib/repobrowse/git_src_tree_html.rb +118 -0
- data/lib/repobrowse/html.rb +66 -0
- data/lib/repobrowse/limit_rd.rb +86 -0
- data/lib/repobrowse/pipe_body.rb +25 -0
- data/lib/repobrowse/repo.rb +21 -0
- data/lib/repobrowse/static.rb +96 -0
- data/repobrowse.gemspec +29 -0
- data/test/covshow.rb +30 -0
- data/test/git.fast-import-data +101 -0
- data/test/helper.rb +182 -0
- data/test/test_config.rb +29 -0
- data/test/test_git.rb +15 -0
- data/test/test_git_atom.rb +27 -0
- data/test/test_git_clone.rb +73 -0
- data/test/test_git_patch.rb +44 -0
- data/test/test_git_raw_tree_html.rb +34 -0
- data/test/test_git_show.rb +17 -0
- data/test/test_html.rb +23 -0
- metadata +139 -0
@@ -0,0 +1,320 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# Copyright (C) 2017-2018 all contributors <repobrowse-public@80x24.org>
|
3
|
+
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
|
4
|
+
# frozen_string_literal: true
|
5
|
+
require_relative 'html'
|
6
|
+
|
7
|
+
class Repobrowse::GitCommitHTML < Repobrowse::HTML
|
8
|
+
|
9
|
+
# rugged doesn't seem to have a way to show diffstats, decorations
|
10
|
+
# or combined diffs (--cc/--combined) for merges, so use git-show here
|
11
|
+
CMT_CMD = %w(show -z --numstat -p --encoding=UTF-8 --pretty=format:%p%n%d%x00)
|
12
|
+
|
13
|
+
def commit_header(env, repo, cmt)
|
14
|
+
msg = Rugged.prettify_message(cmt.message)
|
15
|
+
subject, body = msg.split(/\r?\n\r?\n/, 2)
|
16
|
+
subject.strip!
|
17
|
+
ht(subject)
|
18
|
+
start(subject, repo)
|
19
|
+
parents = cmt.parents
|
20
|
+
oid = @commit = cmt.oid
|
21
|
+
@buf << ' commit ' + oid
|
22
|
+
@mhelp = nil
|
23
|
+
case parents.size
|
24
|
+
when 0
|
25
|
+
when 1
|
26
|
+
@buf << %Q[ (<a\nhref="#{oid}.patch">patch</a>)]
|
27
|
+
pfx = ' parent'
|
28
|
+
else
|
29
|
+
@mhelp = "\n This is a merge, showing combined diff:\n\n"
|
30
|
+
pfx = ' parents'
|
31
|
+
end
|
32
|
+
|
33
|
+
@buf << "\n "
|
34
|
+
@buf << %Q(tree <a\nrel=nofollow\nhref="src/#{oid}">#{cmt.tree_oid}</a>\n)
|
35
|
+
pad = 0
|
36
|
+
idents = [ ' author', 'committer' ].map do |field|
|
37
|
+
x = cmt.__send__(field.strip)
|
38
|
+
name_email = ht(+"#{x[:name]} <#{x[:email]}>")
|
39
|
+
len = name_email.size
|
40
|
+
pad = len if len > pad
|
41
|
+
[ field, name_email, x[:time].strftime('%Y-%m-%d %k:%M:%S %z') ]
|
42
|
+
end
|
43
|
+
idents.each do |field, name_email, time|
|
44
|
+
@buf << "#{field} #{[name_email].pack("A#{pad}")}\t#{time}\n"
|
45
|
+
end
|
46
|
+
@repo = repo
|
47
|
+
cmd = CMT_CMD.dup
|
48
|
+
cmd << @commit
|
49
|
+
cmd << '--'
|
50
|
+
@rd = repo.driver.popen(cmd, encoding: Encoding::UTF_8)
|
51
|
+
abbr = @rd.gets(chomp: true).split(' ')
|
52
|
+
parents.each_with_index do |pt, i|
|
53
|
+
title = Rugged.prettify_message(pt.summary)
|
54
|
+
title.strip!
|
55
|
+
@buf << %Q(#{pfx} <a id=P#{i}\nhref="#{pt.oid}">#{abbr[i]}</a> #{ht(title)}\n)
|
56
|
+
pfx = ' '
|
57
|
+
end
|
58
|
+
refnames = @rd.gets("\0", chomp: true)
|
59
|
+
@buf << "\n<b>#{subject}</b>#{ht(refnames)}\n\n"
|
60
|
+
@buf << ht(body) if body
|
61
|
+
@buf << "<a\nid=D>---</a>\n"
|
62
|
+
@anchors = {}
|
63
|
+
@parents = parents
|
64
|
+
@nchg = @nadd = @ndel = 0
|
65
|
+
@state = :stat_begin
|
66
|
+
end
|
67
|
+
|
68
|
+
# do not break anchor links if the combined diff doesn't show changes:
|
69
|
+
def show_unchanged
|
70
|
+
unchanged = @anchors.keys.sort!
|
71
|
+
unchanged[0] or return ''
|
72
|
+
buf = +<<EOS
|
73
|
+
|
74
|
+
There are uninteresting changes from this merge.
|
75
|
+
See the <a\nhref="#P0">parents</a>, or view final state(s) below:
|
76
|
+
|
77
|
+
EOS
|
78
|
+
|
79
|
+
unchanged.each do |anchor|
|
80
|
+
fn = @repo.driver.git_unquote(@anchors[anchor])
|
81
|
+
href = ha(+"src/#@commit:#{fn}")
|
82
|
+
buf << "\t<a\nrel=nofollow\nid=#{
|
83
|
+
anchor}
|
84
|
+
}\nhref=#{href}>#{ht(+fn)}</a>\n"
|
85
|
+
end
|
86
|
+
buf
|
87
|
+
end
|
88
|
+
|
89
|
+
def diffstat_rename_line(from, to)
|
90
|
+
anchor = -to_anchor(to)
|
91
|
+
@anchors[anchor] = to
|
92
|
+
from_parts = from.split('/')
|
93
|
+
to_parts = to.split('/')
|
94
|
+
base = []
|
95
|
+
while to_parts[0] && to_parts[0] == from_parts[0]
|
96
|
+
base << to_parts.shift
|
97
|
+
from_parts.shift
|
98
|
+
end
|
99
|
+
from = from_parts.join('/')
|
100
|
+
to = to_parts.join('/')
|
101
|
+
to = %Q(<a\nhref="##{anchor}">#{ht(to)}</a>)
|
102
|
+
if base[0]
|
103
|
+
base = ht(base.join('/'))
|
104
|
+
"#{base}/{#{from} => #{to}}"
|
105
|
+
else
|
106
|
+
"#{from} => #{to}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def diffstat_line(line)
|
111
|
+
line =~ /\A(\S+)\t+(\S+)\t+(.*)/ or die("bad stat line: #{line.inspect}")
|
112
|
+
add = -$1
|
113
|
+
del = -$2
|
114
|
+
fn = -$3
|
115
|
+
if fn != '' # normal modification
|
116
|
+
anchor = -to_anchor(fn)
|
117
|
+
@anchors[anchor] = -fn
|
118
|
+
line = %Q(<a\nhref="##{anchor}">#{ht(fn.dup)}</a>)
|
119
|
+
else # rename
|
120
|
+
from = @rd.gets("\0", chomp: true) or die('EOF rename (from)')
|
121
|
+
to = @rd.gets("\0", chomp: true) or die('EOF rename (to)')
|
122
|
+
line = diffstat_rename_line(from, to);
|
123
|
+
end
|
124
|
+
|
125
|
+
# text changes show numerically, Binary does not
|
126
|
+
if add =~ /\A\d+\z/ && del =~ /\A\d+\z/
|
127
|
+
@nadd += add.to_i
|
128
|
+
@ndel += del.to_i
|
129
|
+
add = "+#{add}"
|
130
|
+
del = "-#{del}"
|
131
|
+
else # just in case...
|
132
|
+
ht(add)
|
133
|
+
ht(del)
|
134
|
+
end
|
135
|
+
@nchg += 1
|
136
|
+
" #{sprintf('% 6s/%-6s', del, add)}\t#{line}\n"
|
137
|
+
end
|
138
|
+
|
139
|
+
def diffstat_end
|
140
|
+
ret = +"\n #@nchg "
|
141
|
+
ret << (@nchg == 1 ? 'file changed, ' : 'files changed, ')
|
142
|
+
ret << @nadd.to_s
|
143
|
+
ret << (@nadd == 1 ? ' insertion(+), ' : ' insertions(+), ')
|
144
|
+
ret << @ndel.to_s
|
145
|
+
ret << (@ndel == 1 ? " deletion(-)\n\n" : " deletions(-)\n\n")
|
146
|
+
end
|
147
|
+
|
148
|
+
def die(msg)
|
149
|
+
raise RuntimeError, "#{msg} (#@commit)", []
|
150
|
+
end
|
151
|
+
|
152
|
+
# diff --git a/foo.c b/bar.c
|
153
|
+
def git_diff_ab_hdr(fa, fb)
|
154
|
+
html_a = ht(fa.dup)
|
155
|
+
html_b = ht(fb.dup)
|
156
|
+
fa = @repo.driver.git_unquote(fa)
|
157
|
+
fb = @repo.driver.git_unquote(fb)
|
158
|
+
fa.sub!(%r{\Aa/}, '')
|
159
|
+
fb.sub!(%r{\Ab/}, '')
|
160
|
+
anchor = -to_anchor(fb)
|
161
|
+
@anchors.delete(anchor)
|
162
|
+
@fa = fa
|
163
|
+
@fb = fb
|
164
|
+
# not wasting bandwidth on links here
|
165
|
+
# links in hunk headers are far more useful with line offsets
|
166
|
+
%Q(<a\nid="#{anchor}">diff</a> --git #{html_a} #{html_b}\n)
|
167
|
+
end
|
168
|
+
|
169
|
+
# diff (--cc|--combined)
|
170
|
+
def git_diff_cc_hdr(combined, path)
|
171
|
+
html_path = ht(path.dup)
|
172
|
+
path = @repo.driver.git_unquote(path)
|
173
|
+
anchor = to_anchor(path)
|
174
|
+
@anchors.delete(anchor)
|
175
|
+
@path_cc = path
|
176
|
+
%Q(<a\nid="#{anchor}">diff</a> --#{combined} #{html_path}\n)
|
177
|
+
end
|
178
|
+
|
179
|
+
# index abcdef89..01234567
|
180
|
+
def git_diff_ab_index(da, db, tail)
|
181
|
+
# not wasting bandwidth on links here, yet
|
182
|
+
# links in hunk headers are far more useful with line offsets
|
183
|
+
"index #{da}..#{db}#{ht(tail)}\n"
|
184
|
+
end
|
185
|
+
|
186
|
+
def git_diff_src_link(ref, file, lineno, text)
|
187
|
+
fragment = lineno ? "#n#{lineno}" : ''
|
188
|
+
href = ha(+"src/#{ref}:#{file}#{fragment}")
|
189
|
+
%Q(<a\nrel=nofollow\nhref=#{href}>#{text}</a>)
|
190
|
+
end
|
191
|
+
|
192
|
+
# @@ -1,2 +3,4 @@ (regular diff)
|
193
|
+
def git_diff_ab_hunk(ca, cb, ctx)
|
194
|
+
na = ca.match(/\A-(\d+)/)[1]
|
195
|
+
nb = cb.match(/\A\+(\d+)/)[1]
|
196
|
+
|
197
|
+
# we add "rel=nofollow" here to reduce load on search engines, here
|
198
|
+
rv = +'@@ '
|
199
|
+
rv << (na == '0' ? ca : git_diff_src_link(@parents[0], @fa, na, ca))
|
200
|
+
rv << ' '
|
201
|
+
rv << (nb == '0' ? cb : git_diff_src_link(@commit, @fb, nb, cb))
|
202
|
+
rv << " @@#{ht(ctx)}\n"
|
203
|
+
end
|
204
|
+
|
205
|
+
# index abcdef09,01234567..76543210
|
206
|
+
def git_diff_cc_index(before, last, tail)
|
207
|
+
ht(tail)
|
208
|
+
@parent_objs_cc = before.split(',')
|
209
|
+
|
210
|
+
# not wasting bandwidth on links here, yet
|
211
|
+
# links in hunk headers are far more useful with line offsets
|
212
|
+
"index #{before}..#{last}#{tail}\n"
|
213
|
+
end
|
214
|
+
|
215
|
+
# @@@ -1,2 -3,4 +5,6 @@@ (combined diff)
|
216
|
+
def git_diff_cc_hunk(at, offs, ctx)
|
217
|
+
offs = offs.split(' ')
|
218
|
+
last = offs.pop
|
219
|
+
rv = at.dup
|
220
|
+
|
221
|
+
offs.each_with_index do |off, i|
|
222
|
+
parent = @parents[i]
|
223
|
+
blob = @parent_objs_cc[i]
|
224
|
+
lineno = off.match(/\A-(\d+)/)[1]
|
225
|
+
|
226
|
+
if lineno == '0' # new file (does this happen with --cc?)
|
227
|
+
rv << " #{off}"
|
228
|
+
else
|
229
|
+
href = ha(+"src/#{parent}?id=#{blob}#n#{lineno}")
|
230
|
+
rv << %Q( <a\nhref=#{href}>#{off}</a>)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
lineno = last.match(/\A\+(\d+)/)[1]
|
235
|
+
rv << ' '
|
236
|
+
if lineno == '0' # deleted file (does this happen with --cc?)
|
237
|
+
rv << last
|
238
|
+
else
|
239
|
+
rv << git_diff_src_link(@commit, @path_cc, lineno, last)
|
240
|
+
end
|
241
|
+
rv << " #{at}#{ht(ctx)}\n"
|
242
|
+
end
|
243
|
+
|
244
|
+
def diff_line(line)
|
245
|
+
# dfa and dfb class names match public-inbox search term prefix
|
246
|
+
case line
|
247
|
+
when /\A\+/
|
248
|
+
%Q{<span\nclass="dfa">#{ht(line.chomp!)}</span>\n}
|
249
|
+
when /\A\-/
|
250
|
+
%Q{<span\nclass="dfb">#{ht(line.chomp!)}</span>\n}
|
251
|
+
when %r{\Adiff --git ("?a/.+) ("?b/.+)\n\z} # regular
|
252
|
+
git_diff_ab_hdr($1, $2)
|
253
|
+
when /\Adiff --(cc|combined) (.+)\n\z/ # merge
|
254
|
+
git_diff_cc_hdr($1, $2)
|
255
|
+
when /\Aindex ([a-f0-9]+)\.\.([a-f0-9]+)(.*)\n\z/ # regular
|
256
|
+
git_diff_ab_index($1, $2, $3)
|
257
|
+
when /\A@@ ([\d,\+\-]+) ([\d,\+\-]+) @@(.*)\n\z/ # regular
|
258
|
+
git_diff_ab_hunk($1, $2, $3)
|
259
|
+
when /\Aindex ([a-f0-9]+,[^\.]+)\.\.([a-f0-9]+)(.*)\n\z/ # --cc
|
260
|
+
git_diff_cc_index($1, $2, $3)
|
261
|
+
when /\A(@@@+) (\S+.*\S+) @@@+(.*)\n\z/ # --cc
|
262
|
+
git_diff_cc_hunk($1, $2, $3)
|
263
|
+
when nil
|
264
|
+
diff_done
|
265
|
+
else
|
266
|
+
ht(line)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def diff_done
|
271
|
+
buf, @mhelp = @mhelp, nil
|
272
|
+
@state = :done
|
273
|
+
"#{buf}#{show_unchanged}</pre></body></html>"
|
274
|
+
end
|
275
|
+
|
276
|
+
# called by the Rack server
|
277
|
+
def each
|
278
|
+
buf = @buf
|
279
|
+
@buf = nil
|
280
|
+
yield buf
|
281
|
+
buf.clear
|
282
|
+
while buf = each_i
|
283
|
+
yield buf
|
284
|
+
buf.clear unless buf.frozen?
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def each_i
|
289
|
+
case @state
|
290
|
+
when :stat_begin
|
291
|
+
# merges start with an extra '\0' before the diffstat
|
292
|
+
# non-merge commits start with an extra '\n', instead
|
293
|
+
sep = @mhelp ? "\0" : "\n"
|
294
|
+
@rd.gets(sep) == sep or die('diffstat line not empty')
|
295
|
+
@state = :stat
|
296
|
+
when :stat
|
297
|
+
case line = @rd.gets("\0", chomp: true)
|
298
|
+
when nil
|
299
|
+
if @mhelp
|
300
|
+
@mhelp = "\n This is a merge, and the combined diff is empty.\n"
|
301
|
+
return diff_done
|
302
|
+
end
|
303
|
+
die('premature EOF')
|
304
|
+
when ''
|
305
|
+
@state = :diff
|
306
|
+
return diffstat_end
|
307
|
+
else
|
308
|
+
return diffstat_line(line)
|
309
|
+
end
|
310
|
+
when :diff
|
311
|
+
return diff_line(@rd.gets)
|
312
|
+
when :done
|
313
|
+
return
|
314
|
+
end while true
|
315
|
+
end
|
316
|
+
|
317
|
+
def close
|
318
|
+
@rd = @rd&.close
|
319
|
+
end
|
320
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2017-2018 all contributors <repobrowse-public@80x24.org>
|
3
|
+
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
|
4
|
+
# frozen_string_literal: true
|
5
|
+
module Repobrowse::GitDisambiguate
|
6
|
+
def git_disambiguate(r, repo, pfx, ref, path)
|
7
|
+
ambiguous = false
|
8
|
+
ambiguous = true if ref.squeeze!('/')
|
9
|
+
ambiguous = true if ref.chomp!('/')
|
10
|
+
if path
|
11
|
+
ambiguous = true if path.chomp!('/')
|
12
|
+
ambiguous = true if path.squeeze!('/')
|
13
|
+
end
|
14
|
+
if ambiguous
|
15
|
+
loc = r.base_url
|
16
|
+
loc << "#{r.script_name}/#{repo.name}/#{pfx}/#{ref}"
|
17
|
+
loc << ":#{path}" if path
|
18
|
+
r.redirect(loc) # throws
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# Copyright (C) 2017-2018 all contributors <repobrowse-public@80x24.org>
|
2
|
+
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
# provides smart HTTP cloning for git repos
|
8
|
+
module Repobrowse::GitHTTPBackend
|
9
|
+
|
10
|
+
# used to provide streaming Rack response body
|
11
|
+
class EachWrap
|
12
|
+
def initialize(io)
|
13
|
+
@io = io
|
14
|
+
end
|
15
|
+
|
16
|
+
def each
|
17
|
+
begin
|
18
|
+
buf = @io.readpartial(16384, buf)
|
19
|
+
yield buf
|
20
|
+
rescue EOFError
|
21
|
+
return
|
22
|
+
ensure
|
23
|
+
buf&.clear
|
24
|
+
end while true
|
25
|
+
end
|
26
|
+
|
27
|
+
def close
|
28
|
+
@io.close
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def input_to_file(env)
|
33
|
+
tmp = Tempfile.new('git-http-backend-in')
|
34
|
+
tmp.unlink
|
35
|
+
tmp.sync = true
|
36
|
+
IO.copy_stream(env['rack.input'], tmp.to_io)
|
37
|
+
tmp.rewind
|
38
|
+
tmp
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_http_backend(r, repo, path)
|
42
|
+
penv = {
|
43
|
+
'GIT_HTTP_EXPORT_ALL' => '1',
|
44
|
+
'PATH_TRANSLATED' => -"#{repo.path}/#{path}",
|
45
|
+
}
|
46
|
+
env = r.env
|
47
|
+
# GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL
|
48
|
+
# may be set in the server-process and are passed as-is
|
49
|
+
%w(QUERY_STRING REMOTE_USER REMOTE_ADDR HTTP_CONTENT_ENCODING
|
50
|
+
CONTENT_TYPE SERVER_PROTOCOL REQUEST_METHOD).each do |k|
|
51
|
+
v = env[k] and penv[k] = v
|
52
|
+
end
|
53
|
+
IO.popen(penv, %W(git http-backend), in: input_to_file(env))
|
54
|
+
rescue => e
|
55
|
+
r_err(r, "E: #{e.message} (#{e.class}) on #{repo.path}")
|
56
|
+
end
|
57
|
+
|
58
|
+
# returns undef if 403 so it falls back to dumb HTTP
|
59
|
+
def serve_smart(r, repo, path)
|
60
|
+
rd = run_http_backend(r, repo, path)
|
61
|
+
code = 200
|
62
|
+
h = {}
|
63
|
+
|
64
|
+
# parse CGI response headers
|
65
|
+
case rd.gets
|
66
|
+
when %r{\AStatus:\s+(\d+)}i
|
67
|
+
code = $1.to_i
|
68
|
+
when %r{\A([^:]+):\s*(.*)\r\n\z}
|
69
|
+
h[-$1] = -$2
|
70
|
+
when "\r\n"
|
71
|
+
break # headers done onto the body
|
72
|
+
when nil
|
73
|
+
rd.close
|
74
|
+
r_err(r, "unexpected EOF on git http-backend in #{repo.path}")
|
75
|
+
end while true
|
76
|
+
|
77
|
+
r.halt [ code, h, EachWrap.new(rd) ]
|
78
|
+
end
|
79
|
+
|
80
|
+
def git_http_backend_routes(r, repo)
|
81
|
+
r.is(path = 'git-upload-pack') { r.post { serve_smart(r, repo, path) } }
|
82
|
+
r.is(path = 'info/refs') {
|
83
|
+
# QUERY_STRING has exactly one parameter
|
84
|
+
# See https://80x24.org/git/src/v2.16.1:Documentation/technical/http-protocol.txt
|
85
|
+
if /\Aservice=git-\w+-pack\z/ =~ r.env['QUERY_STRING']
|
86
|
+
r.get { serve_smart(r, repo, path) }
|
87
|
+
else
|
88
|
+
static(r, -"#{repo.path}/#{path}", 'text/plain', nil)
|
89
|
+
end
|
90
|
+
}
|
91
|
+
%w(HEAD cloneurl description
|
92
|
+
objects/info/http-alternates
|
93
|
+
objects/info/alternates
|
94
|
+
objects/info/packs).each { |txt|
|
95
|
+
r.is(txt) { static(r, -"#{repo.path}/#{txt}", 'text/plain', nil) }
|
96
|
+
}
|
97
|
+
r.is('objects', :git_x2, :git_x38) { |x2, x38|
|
98
|
+
static(r, -"#{repo.path}/objects/#{x2}/#{x38}",
|
99
|
+
'application/x-git-loose-object')
|
100
|
+
}
|
101
|
+
[ :git_pack, 'application/x-git-packed-objects',
|
102
|
+
:git_pack_idx, 'application/x-git-packed-objects-toc'
|
103
|
+
].each_slice(2) { |sym, type|
|
104
|
+
r.is('objects/pack', :sym) { |o|
|
105
|
+
static(r, -"#{repo.path}/objects/pack/#{o}", type)
|
106
|
+
}
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|