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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +5 -0
  3. data/.gitignore +3 -0
  4. data/COPYING +661 -0
  5. data/GNUmakefile +38 -0
  6. data/MANIFEST +42 -0
  7. data/README +35 -0
  8. data/lib/repobrowse.rb +7 -0
  9. data/lib/repobrowse/app.rb +60 -0
  10. data/lib/repobrowse/config.rb +66 -0
  11. data/lib/repobrowse/error.rb +21 -0
  12. data/lib/repobrowse/escape.rb +28 -0
  13. data/lib/repobrowse/git.rb +71 -0
  14. data/lib/repobrowse/git_atom.rb +109 -0
  15. data/lib/repobrowse/git_commit_html.rb +320 -0
  16. data/lib/repobrowse/git_disambiguate.rb +21 -0
  17. data/lib/repobrowse/git_http_backend.rb +109 -0
  18. data/lib/repobrowse/git_log.rb +4 -0
  19. data/lib/repobrowse/git_patch.rb +55 -0
  20. data/lib/repobrowse/git_raw.rb +50 -0
  21. data/lib/repobrowse/git_raw_tree_html.rb +50 -0
  22. data/lib/repobrowse/git_show.rb +32 -0
  23. data/lib/repobrowse/git_src.rb +37 -0
  24. data/lib/repobrowse/git_src_blob_html.rb +89 -0
  25. data/lib/repobrowse/git_src_tree_html.rb +118 -0
  26. data/lib/repobrowse/html.rb +66 -0
  27. data/lib/repobrowse/limit_rd.rb +86 -0
  28. data/lib/repobrowse/pipe_body.rb +25 -0
  29. data/lib/repobrowse/repo.rb +21 -0
  30. data/lib/repobrowse/static.rb +96 -0
  31. data/repobrowse.gemspec +29 -0
  32. data/test/covshow.rb +30 -0
  33. data/test/git.fast-import-data +101 -0
  34. data/test/helper.rb +182 -0
  35. data/test/test_config.rb +29 -0
  36. data/test/test_git.rb +15 -0
  37. data/test/test_git_atom.rb +27 -0
  38. data/test/test_git_clone.rb +73 -0
  39. data/test/test_git_patch.rb +44 -0
  40. data/test/test_git_raw_tree_html.rb +34 -0
  41. data/test/test_git_show.rb +17 -0
  42. data/test/test_html.rb +23 -0
  43. metadata +139 -0
@@ -0,0 +1,86 @@
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: false
5
+
6
+ class Repobrowse::LimitRd
7
+ def initialize(rd, size, buf, &blk)
8
+ @rd = rd # git cat-file --batch output
9
+ @left = size == 0 ? nil : size
10
+ @buf = buf
11
+ @on_close = blk
12
+ @peek = nil
13
+ end
14
+
15
+ # called by Rack server in ensure
16
+ def close
17
+ @on_close&.call(self) # allows our @rd to be reused
18
+ @buf&.clear
19
+ @peek&.clear
20
+ @on_close = nil
21
+ end
22
+
23
+ # used to determine if a file is binary or text
24
+ def peek(len = 8000) # 8000 bytes is the same size used by git
25
+ @peek = read(len, @buf)
26
+ end
27
+
28
+ # called by Rack server
29
+ def each
30
+ peek, @peek = @peek, nil
31
+ yield peek if peek
32
+ while read(16384, @buf)
33
+ yield @buf
34
+ end
35
+ end
36
+
37
+ # non-Rack response body interface
38
+ def read(len = nil, buf = nil)
39
+ raise RuntimeError, 'not #read is compatible with #peek' if @peek
40
+ if @left
41
+ len = @left if len.nil? || len > @left
42
+ ret = @rd.read(len, buf)
43
+ @left -= ret.bytesize if ret
44
+ @left = nil if @left == 0
45
+ ret
46
+ else
47
+ buf&.clear
48
+ len ? nil : ''
49
+ end
50
+ end
51
+
52
+ # non-Rack response body interface
53
+ def gets(sep = $/, limit = nil, chomp: false)
54
+ if Integer === sep && limit.nil?
55
+ limit = sep
56
+ sep = $/
57
+ end
58
+ raise RuntimeError, 'not #read is compatible with #peek' if @peek
59
+ return if @left.nil?
60
+ return '' if limit == 0
61
+ limit = @left if limit.nil? || limit > @left
62
+ if limit == 0
63
+ @left = nil
64
+ return nil
65
+ end
66
+ buf = @rd.gets(sep, limit)
67
+ if buf
68
+ @left -= buf.bytesize
69
+ @left = nil if @left == 0
70
+
71
+ # we must chomp ourselves for accounting @left
72
+ buf.chomp!(sep) if chomp
73
+ end
74
+ buf
75
+ end
76
+
77
+ # we must drain the buffer if the reader aborted prematurely
78
+ def drain
79
+ n = @left or return
80
+ max = 16384
81
+ while n > 0
82
+ len = n > max ? max : n
83
+ @rd.read(len, @buf) and n -= @buf.bytesize
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,25 @@
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
+ class Repobrowse::PipeBody
6
+ attr_reader :to_io
7
+
8
+ def initialize(io, buf)
9
+ @to_io = io
10
+ @buf = buf
11
+ end
12
+
13
+ # called by Rack server
14
+ def each
15
+ begin
16
+ yield @buf
17
+ end while @to_io.read(0x4000, @buf)
18
+ end
19
+
20
+ # called by Rack server
21
+ def close
22
+ @buf.clear
23
+ @to_io.close
24
+ end
25
+ end
@@ -0,0 +1,21 @@
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
+ # class for represpenting an inbox git repository.
6
+ class Repobrowse::Repo
7
+ attr_reader :driver
8
+ attr_reader :name
9
+ attr_reader :path
10
+ attr_reader :lineno_prefix
11
+
12
+ def initialize(opts)
13
+ @lineno_prefix = 'n' # 'l' for gitweb conversions
14
+ opts.each { |k, v| instance_variable_set "@#{k}", v }
15
+ case @vcs
16
+ when 'git'
17
+ @driver = Repobrowse::Git.new(@path)
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,96 @@
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
+ require_relative 'limit_rd'
5
+
6
+ module Repobrowse::Static
7
+
8
+ # Rack response body
9
+ class F < File
10
+ def each
11
+ while buf = read(8192, buf)
12
+ yield buf
13
+ end
14
+ ensure
15
+ buf&.clear
16
+ end
17
+ end
18
+
19
+ def fopen(r, pathname)
20
+ F.open(pathname)
21
+ rescue SystemCallError
22
+ r404(r)
23
+ end
24
+
25
+ def prepare_range(env, r, fp, h, beg_s, fin_s, size)
26
+ code = 200
27
+ len = size
28
+ beg = beg_s.to_i
29
+ fin = fin_s.to_i
30
+ if beg_s == ''
31
+ if fin_s != '' # "bytes=-end" => last N bytes
32
+ beg = size - fin
33
+ beg = 0 if beg < 0
34
+ fin = size - 1
35
+ code = 206
36
+ else
37
+ code = 416
38
+ end
39
+ else
40
+ if beg > size
41
+ code = 416
42
+ elsif fin_s == '' || fin >= size
43
+ fin = size - 1
44
+ code = 206
45
+ elsif fin < size
46
+ code = 206
47
+ else
48
+ code = 416
49
+ end
50
+ end
51
+ if code == 206
52
+ len = fin - beg + 1;
53
+ if len <= 0
54
+ code = 416
55
+ else
56
+ # most Rack servers do not handle range requests correctly
57
+ # if they use .to_path
58
+ fp.autoclose = false
59
+ fp.seek(beg, IO::SEEK_SET) or r.halt [ 500, [], [] ]
60
+ fp = Repobrowse::LimitRd.new(fp, fin - beg + 1, ''.b)
61
+ h['Accept-Ranges'] = -'bytes'
62
+ h['Content-Range'] = "bytes #{beg}-#{fin}/#{size}"
63
+ end
64
+ end
65
+ if code == 416
66
+ h['Content-Range'] = -"bytes */#{size}"
67
+ r.halt [ 416, h, [] ]
68
+ end
69
+ [ code, len, fp ]
70
+ end
71
+
72
+ # expires in 1 year
73
+ def static(r, pathname, type, exp = 31536000)
74
+ r.get do
75
+ fp = fopen(r, pathname)
76
+ h = { 'Content-Type' => type }
77
+ if exp
78
+ h['Expires'] = -((Time.now + exp).httpdate)
79
+ h['Cache-Control'] = -"public, max-age=#{exp}"
80
+ else
81
+ h['Expires'] = 'Fri, 01 Jan 1980 00:00:00 GMT'
82
+ h['Pragma'] = 'no-cache'
83
+ h['Cache-Control'] = 'no-cache, max-age=0, must-revalidate'
84
+ end
85
+ # TODO: If-Modified-Since and Last-Modified?
86
+ code = 200
87
+ st = fp.stat
88
+ size = st.size
89
+ if env['HTTP_RANGE'] =~ /\bbytes=(\d*)-(\d*)\z/
90
+ code, size, fp = prepare_range(env, r, fp, h, $1, $2, size)
91
+ end
92
+ h['Content-Length'] = -size.to_s
93
+ r.halt [ code, h, fp ]
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,29 @@
1
+ git_manifest = `git ls-files 2>/dev/null`.split("\n")
2
+ manifest = File.exist?('MANIFEST') ?
3
+ File.readlines('MANIFEST').map!(&:chomp).delete_if(&:empty?) : git_manifest
4
+ if git_manifest[0] && manifest != git_manifest
5
+ tmp = "MANIFEST.#$$.tmp"
6
+ File.open(tmp, 'w') { |fp| fp.puts(git_manifest.join("\n")) }
7
+ File.rename(tmp, 'MANIFEST')
8
+ system('git add MANIFEST')
9
+ end
10
+
11
+ Gem::Specification.new do |s|
12
+ s.name = %q{repobrowse}
13
+ s.version = (ENV['VERSION'] || '0.0.0').dup
14
+ s.homepage = 'https://80x24.org/repobrowse/'
15
+ s.authors = ['repobrowse hackers']
16
+ s.description = File.read('README').split("\n\n")[1]
17
+ s.email = %q{repobrowse-public@80x24.org}
18
+ s.files = manifest
19
+ s.summary = File.readlines('README')[0]
20
+ s.test_files = Dir['test/test_*.rb']
21
+ s.add_development_dependency('test-unit', '~> 3.0')
22
+
23
+ # hope this gets into
24
+ s.add_dependency('roda', '~> 3.3')
25
+ # TODO: make rugged optional when we start supporting other VCS
26
+ s.add_dependency('rugged', '~> 0.24') # whatever's in Debian stretch
27
+ s.required_ruby_version = '>= 2.3'
28
+ s.licenses = %w(AGPL-3.0+)
29
+ end
@@ -0,0 +1,30 @@
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
+ # this works with the __covmerge method in test/helper.rb
6
+ # run this file after all tests are run
7
+
8
+ # load the merged dump data
9
+ res = Marshal.load(IO.binread("coverage.dump"))
10
+
11
+ # Dirty little text formatter. I tried simplecov but the default
12
+ # HTML+JS is unusable without a GUI (I hate GUIs :P) and it would've
13
+ # taken me longer to search the Internets to find a plain-text
14
+ # formatter I like...
15
+ res.keys.sort.each do |filename|
16
+ cov = res[filename]
17
+ puts "==> #{filename} <=="
18
+ File.readlines(filename).each_with_index do |line, i|
19
+ n = cov[i]
20
+ if n == 0 # BAD
21
+ print(" *** 0 #{line}")
22
+ elsif n
23
+ printf("% 7u %s", n, line)
24
+ elsif line =~ /\S/ # probably a line with just "end" in it
25
+ print(" #{line}")
26
+ else # blank line
27
+ print "\n" # don't output trailing whitespace on blank lines
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,101 @@
1
+ blob
2
+ mark :1
3
+ data 6
4
+ hello
5
+
6
+ reset refs/heads/header
7
+ commit refs/heads/header
8
+ mark :2
9
+ author AU Thor <e@example.com> 0 +0000
10
+ committer AU Thor <e@example.com> 0 +0000
11
+ data 8
12
+ initial
13
+ M 100644 :1 foo.txt
14
+
15
+ blob
16
+ mark :3
17
+ data 12
18
+ hello
19
+ world
20
+
21
+ commit refs/heads/master
22
+ mark :4
23
+ author AU Thor <e@example.com> 0 +0000
24
+ committer AU Thor <e@example.com> 0 +0000
25
+ data 7
26
+ second
27
+ from :2
28
+ M 100644 :3 foo.txt
29
+
30
+ blob
31
+ mark :5
32
+ data 12
33
+ -----
34
+ hello
35
+
36
+ commit refs/heads/header
37
+ mark :6
38
+ author AU Thor <e@example.com> 0 +0000
39
+ committer AU Thor <e@example.com> 0 +0000
40
+ data 11
41
+ add header
42
+ from :2
43
+ M 100644 :5 foo.txt
44
+
45
+ blob
46
+ mark :7
47
+ data 18
48
+ -----
49
+ hello
50
+ world
51
+
52
+ commit refs/heads/master
53
+ mark :8
54
+ author AU Thor <e@example.com> 0 +0000
55
+ committer AU Thor <e@example.com> 0 +0000
56
+ data 46
57
+ Merge branch 'header'
58
+
59
+ * header:
60
+ add header
61
+ from :4
62
+ merge :6
63
+ M 100644 :7 foo.txt
64
+
65
+ blob
66
+ mark :9
67
+ data 0
68
+
69
+ blob
70
+ mark :10
71
+ data 16
72
+ dir/dur/der/derp
73
+ commit refs/heads/master
74
+ mark :11
75
+ author AU Thor <e@example.com> 0 +0000
76
+ committer AU Thor <e@example.com> 0 +0000
77
+ data 26
78
+ add symlink and deep file
79
+ from :8
80
+ M 100644 :9 dir/dur/der/derp
81
+ M 120000 :10 link
82
+
83
+ blob
84
+ mark :12
85
+ data 78
86
+ [submodule "git"]
87
+ path = git
88
+ url = git://git.kernel.org/pub/scm/git/git.git
89
+
90
+ commit refs/heads/master
91
+ mark :13
92
+ author AU Thor <e@example.com> 0 +0000
93
+ committer AU Thor <e@example.com> 0 +0000
94
+ data 18
95
+ add git submodule
96
+ from :11
97
+ M 100644 :12 .gitmodules
98
+ M 160000 f3adf457e046f92f039353762a78dcb3afb2cb13 git
99
+
100
+ reset refs/heads/master
101
+ from :13
@@ -0,0 +1,182 @@
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
+ $-w = $stdout.sync = $stderr.sync = Thread.abort_on_exception = true
5
+
6
+ # fork-aware coverage data gatherer, see also test/covshow.rb
7
+ if ENV["COVERAGE"]
8
+ require "coverage"
9
+ COVMATCH = %r{\brepobrowse\b}
10
+ COVDUMPFILE = File.expand_path("coverage.dump")
11
+
12
+ def __covmerge
13
+ res = Coverage.result
14
+
15
+ # do not create the file, Makefile does this before any tests run
16
+ File.open(COVDUMPFILE, IO::RDWR) do |covtmp|
17
+ covtmp.binmode
18
+ covtmp.sync = true
19
+
20
+ # we own this file (at least until somebody tries to use NFS :x)
21
+ covtmp.flock(File::LOCK_EX)
22
+
23
+ prev = covtmp.read
24
+ prev = prev.empty? ? {} : Marshal.load(prev)
25
+ res.each do |filename, counts|
26
+ # filter out stuff that's not in our project
27
+ COVMATCH =~ filename or next
28
+
29
+ # For compatibility with https://bugs.ruby-lang.org/issues/9508
30
+ # TODO: support those features if that gets merged into mainline
31
+ unless Array === counts
32
+ counts = counts[:lines]
33
+ end
34
+
35
+ merge = prev[filename] || []
36
+ merge = merge
37
+ counts.each_with_index do |count, i|
38
+ count or next
39
+ merge[i] = (merge[i] || 0) + count
40
+ end
41
+ prev[filename] = merge
42
+ end
43
+ covtmp.rewind
44
+ covtmp.truncate(0)
45
+ covtmp.write(Marshal.dump(prev))
46
+ covtmp.flock(File::LOCK_UN)
47
+ end
48
+ end
49
+
50
+ Coverage.start
51
+ require 'test/unit'
52
+ Test::Unit.at_exit { __covmerge }
53
+ else
54
+ require 'test/unit'
55
+ end
56
+ require 'thread'
57
+ require 'fileutils'
58
+ require 'tempfile'
59
+ require 'tmpdir'
60
+ require 'webrick'
61
+ require 'repobrowse'
62
+
63
+ def git_tmp_repo
64
+ Dir.mktmpdir do |tmp_dir|
65
+ git_dir = "#{tmp_dir}/tmp.git"
66
+ assert(system(*%W(git init -q --bare #{git_dir})), 'initialize git dir')
67
+ assert(system(*%W(git --git-dir=#{git_dir} fast-import --quiet),
68
+ in: "#{File.dirname(__FILE__)}/git.fast-import-data"),
69
+ 'fast-import test repo')
70
+ projects = { 'repo.tmp.path' => git_dir }
71
+ @app = Rack::Builder.new { run Repobrowse::App.new(projects) }.to_app
72
+ @req = Rack::MockRequest.new(@app)
73
+ yield tmp_dir, git_dir
74
+ end
75
+ end
76
+
77
+ def rack_server(app)
78
+ readyq = Queue.new
79
+ logq = Queue.new
80
+ host = '127.0.0.1'
81
+ config = {
82
+ Logger: WEBrick::Log.new(logq),
83
+ app: app,
84
+ Host: host,
85
+ Port: 0, # WEBrick will choose a random port
86
+ server: 'webrick',
87
+ }
88
+ config[:AccessLog] = [] unless test_verbose?
89
+
90
+ th = Thread.new do
91
+ srv = Rack::Server.new(config)
92
+ srv.start { |s| readyq.push(s) }
93
+ end
94
+ s = readyq.pop
95
+ assert_instance_of WEBrick::HTTPServer, s
96
+
97
+ # grab the random port chosen by WEBrick
98
+ case l = logq.pop
99
+ when / port=(\d+)\n/
100
+ port = $1.to_i
101
+ break
102
+ end while true
103
+
104
+ Thread.new do
105
+ while l = logq.pop
106
+ warn "W: #{l.inspect}"
107
+ end
108
+ end if test_verbose?
109
+
110
+ yield logq, host, port
111
+ ensure
112
+ s&.shutdown
113
+ th&.join
114
+ end
115
+
116
+ def test_verbose?
117
+ ENV['TEST_VERBOSE'].to_i != 0
118
+ end
119
+
120
+ # Rack::MockRequest doesn't always work since it converts the
121
+ # response into an array without dup-ing Strings, so response
122
+ # bodies which reuse a buffer (when they assume they're writing
123
+ # to a socket) fail
124
+ def req(method, uri, opts = {})
125
+ o = { method: method }.merge!(opts)
126
+ env = @req.class.env_for(uri, o)
127
+ @app.call(env)
128
+ end
129
+
130
+ def body_string(body)
131
+ rv = ''.b
132
+ body.each { |chunk| rv << chunk }
133
+ rv
134
+ ensure
135
+ body.close if body.respond_to?(:close)
136
+ end
137
+
138
+ $tidy = nil
139
+ def tidy_check(input)
140
+ if $tidy.nil?
141
+ case v = `tidy --version 2>/dev/null`
142
+ when /version (\d+)\.\d+\.\d+/ # HTML Tidy for Linux/x86 version 5.2.0
143
+ major = $1.to_i
144
+ $tidy = major
145
+ when ''
146
+ $tidy = false
147
+ else # "HTML Tidy for Linux released on 25 March 2009"
148
+ $tidy = 4
149
+ end
150
+ end
151
+ if $tidy == false
152
+ begin
153
+ body.each { |buf| assert_instance_of(String, buf) }
154
+ ensure
155
+ body.close
156
+ end
157
+ else
158
+ rd, wr = IO.pipe
159
+ th = Thread.new(input) do |body|
160
+ begin
161
+ first = true
162
+ body.each do |chunk|
163
+ if first && $tidy < 5 # HTML5 allows optional type
164
+ first = false
165
+ chunk = chunk.sub(/<style>/, '<style type="application/css">')
166
+ end
167
+ wr.write(chunk)
168
+ end
169
+ ensure
170
+ wr.close
171
+ body.close
172
+ end
173
+ end
174
+ pid = spawn("tidy -q", in: rd, out: IO::NULL)
175
+ _, status = Process.waitpid2(pid)
176
+ assert_predicate status, :success?, status.inspect
177
+ end
178
+ ensure
179
+ th&.join
180
+ rd&.close
181
+ wr&.close
182
+ end