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,38 @@
1
+ # Copyright (C) 2013-2018 all contributors <repobrowse-public@80x24.org>
2
+ # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3
+ all::
4
+ rubybin := $(shell which ruby)
5
+ RUBY = $(rubybin)
6
+ lib := lib
7
+
8
+ # support using eatmydata to speed up tests (apt-get install eatmydata):
9
+ # https://www.flamingspork.com/projects/libeatmydata/
10
+ EATMYDATA =
11
+ -include config.mak
12
+
13
+ ifeq ($(RUBY_TEST_OPTS),)
14
+ ifeq ($(V),1)
15
+ RUBY_TEST_OPTS := -v
16
+ endif
17
+ endif
18
+
19
+ all:: test
20
+ test_units := $(wildcard test/test_*.rb)
21
+ test: $(test_units)
22
+ $(test_units):
23
+ $(EATMYDATA) $(RUBY) -w -I $(lib) $@ $(RUBY_TEST_OPTS)
24
+
25
+ check-warnings:
26
+ @(for i in $$(git ls-files '*.rb'| grep -v '^setup\.rb$$'); \
27
+ do $(RUBY) -d -W2 -c $$i; done) | grep -v '^Syntax OK$$' || :
28
+
29
+ check: test
30
+
31
+ coverage: export COVERAGE=1
32
+ coverage:
33
+ >coverage.dump
34
+ $(MAKE) check
35
+ $(RUBY) ./test/covshow.rb
36
+
37
+ .PHONY: all test $(test_units) NEWS
38
+ .PHONY: check-warnings fix-perms
@@ -0,0 +1,42 @@
1
+ .gitattributes
2
+ .gitignore
3
+ COPYING
4
+ GNUmakefile
5
+ MANIFEST
6
+ README
7
+ TODO
8
+ lib/repobrowse.rb
9
+ lib/repobrowse/app.rb
10
+ lib/repobrowse/config.rb
11
+ lib/repobrowse/error.rb
12
+ lib/repobrowse/escape.rb
13
+ lib/repobrowse/git.rb
14
+ lib/repobrowse/git_atom.rb
15
+ lib/repobrowse/git_commit_html.rb
16
+ lib/repobrowse/git_disambiguate.rb
17
+ lib/repobrowse/git_http_backend.rb
18
+ lib/repobrowse/git_log.rb
19
+ lib/repobrowse/git_patch.rb
20
+ lib/repobrowse/git_raw.rb
21
+ lib/repobrowse/git_raw_tree_html.rb
22
+ lib/repobrowse/git_show.rb
23
+ lib/repobrowse/git_src.rb
24
+ lib/repobrowse/git_src_blob_html.rb
25
+ lib/repobrowse/git_src_tree_html.rb
26
+ lib/repobrowse/html.rb
27
+ lib/repobrowse/limit_rd.rb
28
+ lib/repobrowse/pipe_body.rb
29
+ lib/repobrowse/repo.rb
30
+ lib/repobrowse/static.rb
31
+ repobrowse.gemspec
32
+ test/covshow.rb
33
+ test/git.fast-import-data
34
+ test/helper.rb
35
+ test/test_config.rb
36
+ test/test_git.rb
37
+ test/test_git_atom.rb
38
+ test/test_git_clone.rb
39
+ test/test_git_patch.rb
40
+ test/test_git_raw_tree_html.rb
41
+ test/test_git_show.rb
42
+ test/test_html.rb
data/README ADDED
@@ -0,0 +1,35 @@
1
+ repobrowse - old-fashioned repository browser
2
+ ---------------------------------------------
3
+
4
+ A repository browser with only basic HTML and text; free of
5
+ images and JavaScript. It is intended as a companion to
6
+ public-inbox[1] but may be used on its own. It only supports
7
+ git for now, but support for other Free Software version control
8
+ systems may happen.
9
+
10
+ Features
11
+ --------
12
+
13
+ * low bandwidth usage
14
+ * git smart HTTP clone/fetch support
15
+ * optional public-inbox integration
16
+
17
+ [1] public-inbox is an "archives first" approach to mailing lists
18
+ https://public-inbox.org/
19
+
20
+ Contact
21
+ -------
22
+
23
+ All discussion is archived via public-inbox:
24
+
25
+ https://80x24.org/repobrowse-public/
26
+ nntp://80x24.org/inbox.comp.version-control.repobrowse
27
+
28
+ No subscription will ever be required to mail us, but HTML mail
29
+ will be rejected:
30
+
31
+ repobrowse-public@80x24.org
32
+
33
+
34
+ repobrowse is Copyright (C) 2018 all contributors <repobrowse-public@80x24.org>
35
+ License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
@@ -0,0 +1,7 @@
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
+
6
+ Repobrowse = Module.new # :nodoc:
7
+ require_relative 'repobrowse/app'
@@ -0,0 +1,60 @@
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
+ require_relative 'error'
6
+ require_relative 'static'
7
+ require_relative 'git_http_backend'
8
+ require_relative 'config'
9
+ require 'roda'
10
+
11
+ module Repobrowse::App
12
+
13
+ # main entry point for rackup config.ru files
14
+ def self.new(config)
15
+ case config
16
+ when Hash, String, nil
17
+ config = Repobrowse::Config.new(config)
18
+ end
19
+
20
+ # allow multiple instances of our app:
21
+ c = Class.new(Roda)
22
+ c.class_eval do
23
+ include Repobrowse::Error
24
+ include Repobrowse::Static
25
+ include Repobrowse::GitHTTPBackend
26
+ plugin :symbol_matchers
27
+ plugin :streaming
28
+ plugin :head
29
+
30
+ symbol_matcher :git_pack, %r{(pack-[a-f0-9]{40,}\.pack)}
31
+ symbol_matcher :git_pack_idx, %r{(pack-[a-f0-9]{40,}\.idx)}
32
+ symbol_matcher :git_x2, %r{([a-f0-9]{2})}
33
+ symbol_matcher :git_x38, %r{([a-f0-9]{38,})}
34
+ symbol_matcher :patch, %r{([a-f0-9]{7,})\.patch}
35
+ symbol_matcher :show, %r{([a-f0-9]{7,})}
36
+ symbol_matcher :tree_path, %r{([^:]+)(?::(.+))?}
37
+
38
+ route do |r|
39
+ config.repos.each_value do |repo|
40
+ r.on(repo.name) do
41
+ git_http_backend_routes(r, repo)
42
+ r.is { r.get { repo.driver.summary(r, repo) } }
43
+ r.is('src', :tree_path) { |ref, path|
44
+ r.get { repo.driver.src(r, repo, ref, path) }
45
+ }
46
+ r.is('raw', :tree_path) { |ref, path|
47
+ r.get { repo.driver.raw(r, repo, ref, path) }
48
+ }
49
+ r.is(:show) { |x| r.get { repo.driver.show(r, repo, x) } }
50
+ r.is(:patch) { |x| r.get { repo.driver.patch(r, repo, x) } }
51
+ r.is('atom', :tree_path) { |ref, path|
52
+ r.get { repo.driver.atom(r, repo, ref, path) }
53
+ }
54
+ end # r.on
55
+ end # projects.each
56
+ end # ret.route
57
+ end
58
+ c.freeze.app
59
+ end # self.new
60
+ end
@@ -0,0 +1,66 @@
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
+ require_relative 'repo'
6
+ require_relative 'git'
7
+
8
+ class Repobrowse::Config
9
+ attr_reader :repos
10
+
11
+ def initialize(file = ENV['REPOBROWSE_CONFIG'] ||
12
+ File.expand_path('~/.repobrowse/config'))
13
+ @groups = Hash.new { |h,k| h[k] = [] }
14
+ if Hash === file
15
+ raw = file
16
+ else
17
+ raw = {}
18
+ if File.exist?(file)
19
+ cfg = Rugged::Config.new(file)
20
+ cfg.each_key { |k| raw[-k] = cfg.get_all(k) }
21
+ end
22
+ end
23
+ raw[:snapshots] ||= '-tar.bz2 -tar.xz'
24
+ raw[:groups] = { hidden: [], none: [] }
25
+ @raw = raw
26
+ @repos = {}
27
+ raw.each do |k, v|
28
+ v = Array(v)
29
+ case k
30
+ when %r{\Arepo\.(.+)\.path\z}
31
+ repo_name = -$1
32
+ warn "multiple values defined for #{k.inspect}\n" if v.size > 1
33
+ path = v[-1]
34
+ add_repo(repo_name, path)
35
+ end
36
+ end
37
+ end
38
+
39
+ def add_repo(repo_name, path) # repo_name "git.git"
40
+ rv = { path: path, name: repo_name }
41
+ rv[:snapshot_pfx] = -path.split(%r{/+})[-1].chomp('.git')
42
+ %i[ vcs readme group snapshots ].each do |key|
43
+ rv[key] = @raw["repo.#{repo_name}.#{key}"]
44
+ end
45
+ rv[:snapshots] ||= @raw[:snapshots]
46
+ rv[:snapshots_disabled] = disabled = {}
47
+ rv[:snapshots].split(/\s+/n).each do |type|
48
+ # "-tar.bz2", "-" prefix denotes disabled
49
+ type.sub!(/\A-/, '') and
50
+ disabled[type] = true
51
+ end
52
+ group = rv[:group] ||= :none
53
+ case group
54
+ when Array
55
+ Array(group).each { |g| @groups[g] << repo_name }
56
+ else
57
+ @groups[group] << repo_name
58
+ end
59
+ snap = repo_name.split('/')[-1]
60
+ if (rv[:vcs] ||= 'git') == 'git'
61
+ snap.chomp!('.git') # seems common for git URLs to end in ".git"
62
+ end
63
+ rv[:snapshot_re] = %r{\A\Q#{Regexp.escape(snap)}[-_]}
64
+ @repos[repo_name] = Repobrowse::Repo.new(rv)
65
+ end
66
+ 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
+
6
+ module Repobrowse::Error
7
+ def r_err(r, msg)
8
+ r.env['rack.logger']&.error(msg)
9
+ b = "Internal server error\n"
10
+ r.halt [ 500, { 'Content-Type' => 'text/plain; charset=UTF-8' }, [ b ] ]
11
+ end
12
+
13
+ def r404(r)
14
+ b = "Not Found\n"
15
+ h = {
16
+ 'Content-Type' => -'text/plain; charset=UTF-8',
17
+ 'Content-Length' => -(b.size.to_s),
18
+ }
19
+ r.halt [ 404, h, [ b ] ]
20
+ end
21
+ end
@@ -0,0 +1,28 @@
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
+ require 'cgi'
6
+
7
+ module Repobrowse::Escape
8
+ # like format_sanitized_subject in git.git pretty.c with '%f' format string
9
+ def to_filename(str)
10
+ str = str.sub(/\n.*\z/s, '')
11
+ str.gsub!(/[^A-Za-z0-9_\.]+/, '-')
12
+ str.squeeze!('.')
13
+ str.sub!(/[\.\-]+\z/, '')
14
+ str.sub!(/\A[\.\-]+/, '')
15
+ str
16
+ end
17
+
18
+ def xt(str)
19
+ str.encode!(xml: :text)
20
+ end
21
+
22
+ def xa(str)
23
+ str.encode!(xml: :attr)
24
+ end
25
+
26
+ alias ha xa
27
+ alias ht xt
28
+ end
@@ -0,0 +1,71 @@
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
+ #
6
+ require_relative 'git_show'
7
+ require_relative 'git_patch'
8
+ require_relative 'git_raw'
9
+ require_relative 'git_src'
10
+ require_relative 'git_atom'
11
+ require 'rugged'
12
+
13
+ class Repobrowse::Git
14
+ GIT_ESC = {
15
+ '\\a' => "\a",
16
+ '\\b' => "\b",
17
+ '\\f' => "\f",
18
+ '\\n' => "\n",
19
+ '\\r' => "\r",
20
+ '\\t' => "\t",
21
+ '\\v' => "\013",
22
+ }.freeze
23
+
24
+ def initialize(git_dir)
25
+ @git_dir = -git_dir
26
+ end
27
+
28
+ def rugged
29
+ Thread.current["rgd.#@git_dir".to_sym] ||= Rugged::Repository.new(@git_dir)
30
+ end
31
+
32
+ def description
33
+ s = File.open("#@git_dir/description", &:read)
34
+ s.strip!
35
+ -s
36
+ rescue
37
+ '[error reading $GIT_DIR/description]'
38
+ end
39
+
40
+ def cloneurl
41
+ File.open("#@git_dir/cloneurl", &:readlines).map! { |s| s.strip!; -s }
42
+ rescue
43
+ '[error reading $GIT_DIR/cloneurl]'
44
+ end
45
+
46
+ def popen(argv, encoding: Encoding::ASCII_8BIT)
47
+ cmd = %w(git).concat(argv)
48
+ IO.popen(cmd, chdir: @git_dir, encoding: encoding)
49
+ end
50
+
51
+ def tip
52
+ rugged.head.name
53
+ rescue
54
+ '[error running git symbolic-ref --short HEAD]'
55
+ end
56
+
57
+ def git_unquote(s)
58
+ return s unless s =~ /\A"(.*)"\z/n
59
+ s = $1
60
+ s.gsub!(/\\[abfnrtv]/n, GIT_ESC)
61
+ s.gsub!(/\\([0-7]{1,3})/n) { $1.oct.chr }
62
+ s
63
+ end
64
+
65
+ include Repobrowse::Error
66
+ include Repobrowse::GitShow
67
+ include Repobrowse::GitPatch
68
+ include Repobrowse::GitRaw
69
+ include Repobrowse::GitAtom
70
+ include Repobrowse::GitSrc
71
+ end
@@ -0,0 +1,109 @@
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
+
6
+ module Repobrowse::GitAtom
7
+ DATEFMT = '%Y-%m-%dT%H:%M:%SZ'
8
+
9
+ # running git-log(1) here for now, path-limiting with libgit2|rugged
10
+ # seems like a pain to implement
11
+ ATOM_ARGV = %W(log --no-notes --no-color --no-abbrev --encoding=UTF-8
12
+ --pretty=tformat:%#{%w(H s ct an ae at b).join('%n%')}%x00)
13
+
14
+ class Body
15
+ include Repobrowse::Error
16
+ include Repobrowse::Escape
17
+
18
+ def initialize(rd, r, repo, ref, path)
19
+ @rd = rd
20
+ title = xt(+"#{repo.name} #{ref} #{path || '/'}")
21
+ subtitle = xt(repo.driver.description.dup)
22
+ @url = "#{r.base_url}#{r.script_name}/#{repo.name}"
23
+ # read the first entry to ensure we have something
24
+ updated = +''
25
+ tmp = entry_begin(updated) or r404(r)
26
+
27
+ @buf = +<<~EOF
28
+ <?xml version="1.0"?><feed
29
+ xmlns="http://www.w3.org/2005/Atom"><title>#{
30
+ title}</title><subtitle>#{
31
+ subtitle}</subtitle><link
32
+ rel="alternate"
33
+ type="text/html"
34
+ href=#{xa(@url.dup)}
35
+ /><id>#{
36
+ xt("#@url/log/" + ref)
37
+ }</id><updated>#{updated}</updated>
38
+ EOF
39
+
40
+ @buf << tmp
41
+ end
42
+
43
+ def entry_begin(updated = +'')
44
+ x40 = @rd.gets(chomp: true) or return
45
+ +<<~EOF
46
+ <entry><title>#{ # %s
47
+ xt(@rd.gets(chomp: true))
48
+ }</title><updated>#{ # %ct
49
+ updated.replace(Time.at(@rd.gets.to_i).strftime(DATEFMT))
50
+ }</updated><author><name>#{ # %an
51
+ xt(@rd.gets(chomp: true))
52
+ }</name><email>#{ # %ae
53
+ xt(@rd.gets(chomp: true))
54
+ }</email></author><published>#{ # %at
55
+ Time.at(@rd.gets.to_i).strftime(DATEFMT)
56
+ }</published><link
57
+ rel="alternate"
58
+ type="text/html"
59
+ href=#{xa("#@url/" + x40)
60
+ }/><id>#{x40}</id><content
61
+ type="xhtml"><div
62
+ xmlns="http://www.w3.org/1999/xhtml"><pre
63
+ style="white-space:pre-wrap">
64
+ EOF
65
+ end
66
+
67
+ def entry_body
68
+ tmp = @rd.gets("\0\n", chomp: true)
69
+ tmp.strip!
70
+ xt(tmp)
71
+ end
72
+
73
+ def each
74
+ entry_end = '</pre></div></content></entry>'
75
+ tmp = @buf
76
+ @buf = nil
77
+ yield tmp
78
+ tmp.clear
79
+ tmp = entry_body
80
+ yield tmp
81
+ tmp.clear
82
+ yield entry_end
83
+ while tmp = entry_begin
84
+ yield tmp
85
+ tmp.clear
86
+ tmp = entry_body
87
+ yield tmp
88
+ tmp.clear
89
+ yield entry_end
90
+ end
91
+ yield '</feed>'
92
+ end
93
+
94
+ def close
95
+ @rd.close
96
+ end
97
+ end
98
+
99
+ # /$REPO_NAME/atom/$REF:PATH
100
+ def atom(r, repo, ref, path)
101
+ git_disambiguate(r, repo, 'atom', ref, path)
102
+ max = 50
103
+ argv = [ *ATOM_ARGV, *%W(-#{max} #{ref} --) ]
104
+ argv << path if path
105
+ rd = repo.driver.popen(argv)
106
+ body = Body.new(rd, r, repo, ref, path)
107
+ r.halt([ 200, { 'Content-Type' => 'application/atom+xml' }, body ])
108
+ end
109
+ end