diff options
author | Eric Wong <e@80x24.org> | 2016-12-13 21:56:39 +0000 |
---|---|---|
committer | Eric Wong <e@80x24.org> | 2016-12-14 00:22:55 +0000 |
commit | 00488f0cfe9f81d04cd65d09ea783e860c937401 (patch) | |
tree | 24e4d9282d5e7098fc6cbdcc9d5615c46513661f /lib/PublicInbox/Repobrowse.pm | |
parent | f9d4f28d9761011d3c7ffad9e2c9d1e54b65c519 (diff) | |
parent | 6cdb0221d18b2caed4d0caebf7c20d6eb159497d (diff) | |
download | public-inbox-00488f0cfe9f81d04cd65d09ea783e860c937401.tar.gz |
* origin/repobrowse: (98 commits) t/repobrowse_git_httpd.t: ensure signature exists for split t/repobrowse_git_tree.t: fix test for lack of bold repobrowse: fix alignment of gitlink entries repobrowse: show invalid type for tree views repobrowse: do not bold directory names in tree view repobrowse: reduce checks for response fh repobrowse: larger, short-lived buffer for reading patches repobrowse: reduce risk of callback reference cycles repobrowse: snapshot support for cgit compatibility test: disable warning for Plack::Test::Impl repobrowse: avoid confusing linkification for "diff" repobrowse: git commit view uses pi-httpd.async repobrowse: more consistent variable naming for /commit/ repobrowse: show roughly equivalent "diff-tree" invocation repobrowse: reduce local variables for state management repobrowse: summary handles multiple README types repobrowse: remove bold decorations from diff view repobrowse: common git diff parsing code repobrowse: implement diff view for compatibility examples/repobrowse.psgi: disable Chunked response by default ...
Diffstat (limited to 'lib/PublicInbox/Repobrowse.pm')
-rw-r--r-- | lib/PublicInbox/Repobrowse.pm | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/lib/PublicInbox/Repobrowse.pm b/lib/PublicInbox/Repobrowse.pm new file mode 100644 index 00000000..cdd708e9 --- /dev/null +++ b/lib/PublicInbox/Repobrowse.pm @@ -0,0 +1,151 @@ +# Copyright (C) 2015 all contributors <meta@public-inbox.org> +# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> + +# Version control system (VCS) repository viewer like cgit or gitweb, +# but with optional public-inbox archive integration. +# This uses cgit-compatible PATH_INFO URLs. +# This may be expanded to support other Free Software VCSes such as +# Subversion and Mercurial, so not just git +# +# Same web design principles as PublicInbox::WWW for supporting the +# lowest common denominators (see bottom of Documentation/design_www.txt) +# +# This allows an M:N relationship between "normal" repos for project +# and public-inbox (ssoma) git repositories where N may be zero. +# In other words, repobrowse must work for repositories without +# any public-inbox at all; or with multiple public-inboxes. +# And the rest of public-inbox will always work without a "normal" +# code repo for the project. + +package PublicInbox::Repobrowse; +use strict; +use warnings; +use Plack::Request; +use URI::Escape qw(uri_escape_utf8 uri_unescape); +use PublicInbox::RepobrowseConfig; + +my %CMD = map { lc($_) => $_ } qw(Log Commit Tree Patch Blob Plain Tag Atom + Diff Snapshot); +my %VCS = (git => 'Git'); +my %LOADED; + +sub new { + my ($class, $rconfig) = @_; + $rconfig ||= PublicInbox::RepobrowseConfig->new; + bless { rconfig => $rconfig }, $class; +} + +# simple response for errors +sub r { [ $_[0], ['Content-Type' => 'text/plain'], [ join(' ', @_, "\n") ] ] } + +# Remove trailing slash in URLs which regular humans are likely to read +# in an attempt to improve cache hit ratios. Do not redirect +# plain|patch|blob|fallback endpoints since those could be using +# automated tools which may not follow redirects automatically +# (e.g. curl does not follow 301 unless given "-L") +my %NO_TSLASH = map { $_ => 1 } qw(Log Commit Tree Summary Tag); +sub no_tslash { + my ($cgi) = @_; # Plack::Request + my ($base, $uri); + $base = $cgi->base; + $base =~ s!/+\z!!; + $uri = $cgi->request_uri; + my $qs = ''; + if ($uri =~ s/(\?.+)\z//) { + $qs = $1; + } + if ($uri !~ s!/+\z!!) { + warn "W: buggy redirect? base=$base request_uri=$uri\n"; + } + my $url = $base . $uri . $qs; + [ 301, + [ Location => $url, 'Content-Type' => 'text/plain' ], + [ "Redirecting to $url\n" ] ] +} + +sub root_index { + my ($self) = @_; + my $mod = load_once('PublicInbox::RepobrowseRoot'); + $mod->new->call($self->{rconfig}); # RepobrowseRoot::call +} + +sub call { + my ($self, $env) = @_; + my $cgi = Plack::Request->new($env); + my $method = $cgi->method; + return r(405, 'Method Not Allowed') if ($method !~ /\AGET|HEAD|POST\z/); + + # URL syntax: / repo [ / cmd [ / path ] ] + # cmd: log | commit | diff | tree | view | blob | snapshot + # repo and path (@extra) may both contain '/' + my $path_info = uri_unescape($cgi->path_info); + my (undef, $repo_path, @extra) = split(m{/+}, $path_info, -1); + + return $self->root_index($self) unless length($repo_path); + + my $rconfig = $self->{rconfig}; # RepobrowseConfig + my $repo_info; + until ($repo_info = $rconfig->lookup($repo_path)) { + my $p = shift @extra or last; + $repo_path .= "/$p"; + } + return r404() unless $repo_info; + + my $req = { + repo_info => $repo_info, + extra => \@extra, # path + cgi => $cgi, + rconfig => $rconfig, + env => $env, + }; + my $tslash = 0; + my $cmd = shift @extra; + my $vcs_lc = $repo_info->{vcs}; + my $vcs = $VCS{$vcs_lc} or return r404(); + my $mod; + if (defined $cmd && length $cmd) { + $mod = $CMD{$cmd}; + unless ($mod) { + unshift @extra, $cmd; + $mod = 'Fallback'; + } + $req->{relcmd} = '../' x scalar(@extra); + } else { + $mod = 'Summary'; + $cmd = 'summary'; + if ($path_info =~ m!/\z!) { + $tslash = $path_info =~ tr!/!!; + } else { + my @x = split('/', $repo_path); + $req->{relcmd} = @x > 1 ? "./$x[-1]/" : "/$x[-1]/"; + } + } + while (@extra && $extra[-1] eq '') { + pop @extra; + ++$tslash; + } + + return no_tslash($cgi) if ($tslash && $NO_TSLASH{$mod}); + + $req->{tslash} = $tslash; + $mod = load_once("PublicInbox::Repobrowse$vcs$mod"); + $vcs = load_once("PublicInbox::$vcs"); + $repo_info->{$vcs_lc} ||= $vcs->new($repo_info->{path}); + + $req->{expath} = join('/', @extra); + my $rv = eval { $mod->new->call($cmd, $req) }; # RepobrowseBase::call + $rv || r404(); +} + +sub r404 { r(404, 'Not Found') } + +sub load_once { + my ($mod) = @_; + + return $mod if $LOADED{$mod}; + eval "require $mod"; + $LOADED{$mod} = 1 unless $@; + $mod; +} + +1; |