repobrowse 0.0.0
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.
- 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
|