X-Git-Url: https://git.ladys.computer/Gitweb/blobdiff_plain/d77aeb50057866ca21b2ddd5f758cb5099ddf2ce8c0b0598a5592f7306fb4399..bba687206550fdc47c68becf70143c7e4661165904e10fb139a9046465659152:/gitweb.perl
diff --git a/gitweb.perl b/gitweb.perl
index a1ae09a..032d3cd 100755
--- a/gitweb.perl
+++ b/gitweb.perl
@@ -15,13 +15,13 @@ use CGI::Carp qw(fatalsToBrowser);
use Encode;
use Fcntl ':mode';
use File::Find qw();
+use File::Basename qw(basename);
binmode STDOUT, ':utf8';
our $cgi = new CGI;
our $version = "++GIT_VERSION++";
our $my_url = $cgi->url();
our $my_uri = $cgi->url(-absolute => 1);
-our $rss_link = "";
# core git executable to use
# this can just be "git" if your webserver has a sensible PATH
@@ -35,7 +35,10 @@ our $projectroot = "++GITWEB_PROJECTROOT++";
our $git_temp = "/tmp/gitweb";
# target of the home link on top of all pages
-our $home_link = $my_uri;
+our $home_link = $my_uri || "/";
+
+# string of the home link on top of all pages
+our $home_link_str = "++GITWEB_HOME_LINK_STR++";
# name of your site or organization to appear in page titles
# replace this with something more descriptive for clearer bookmarks
@@ -52,6 +55,10 @@ our $logo = "++GITWEB_LOGO++";
# source of projects list
our $projects_list = "++GITWEB_LIST++";
+# list of git base URLs used for URL to where fetch project from,
+# i.e. full URL is "$git_base_url/$project"
+our @git_base_url_list = ("++GITWEB_BASE_URL++");
+
# default blob_plain mimetype and default charset for text/plain blob
our $default_blob_plain_mimetype = 'text/plain';
our $default_text_plain_charset = undef;
@@ -60,6 +67,68 @@ our $default_text_plain_charset = undef;
# (relative to the current git repository)
our $mimetypes_file = undef;
+# You define site-wide feature defaults here; override them with
+# $GITWEB_CONFIG as necessary.
+our %feature =
+(
+
+# feature => {'sub' => feature-sub, 'override' => allow-override, 'default' => [ default options...]
+
+'blame' => {'sub' => \&feature_blame, 'override' => 0, 'default' => [0]},
+'snapshot' => {'sub' => \&feature_snapshot, 'override' => 0, 'default' => ['x-gzip', 'gz', 'gzip']},
+
+);
+
+sub gitweb_check_feature {
+ my ($name) = @_;
+ return undef unless exists $feature{$name};
+ my ($sub, $override, @defaults) = ($feature{$name}{'sub'},
+ $feature{$name}{'override'},
+ @{$feature{$name}{'default'}});
+ if (!$override) { return @defaults; }
+ return $sub->(@defaults);
+}
+
+# To enable system wide have in $GITWEB_CONFIG
+# $feature{'blame'}{'default'} = [1];
+# To have project specific config enable override in $GITWEB_CONFIG
+# $feature{'blame'}{'override'} = 1;
+# and in project config gitweb.blame = 0|1;
+
+sub feature_blame {
+ my ($val) = git_get_project_config('blame', '--bool');
+
+ if ($val eq 'true') {
+ return 1;
+ } elsif ($val eq 'false') {
+ return 0;
+ }
+
+ return $_[0];
+}
+
+# To disable system wide have in $GITWEB_CONFIG
+# $feature{'snapshot'}{'default'} = [undef];
+# To have project specific config enable override in $GITWEB_CONFIG
+# $feature{'blame'}{'override'} = 1;
+# and in project config gitweb.snapshot = none|gzip|bzip2
+
+sub feature_snapshot {
+ my ($ctype, $suffix, $command) = @_;
+
+ my ($val) = git_get_project_config('snapshot');
+
+ if ($val eq 'gzip') {
+ return ('x-gzip', 'gz', 'gzip');
+ } elsif ($val eq 'bzip2') {
+ return ('x-bzip2', 'bz2', 'bzip2');
+ } elsif ($val eq 'none') {
+ return ();
+ }
+
+ return ($ctype, $suffix, $command);
+}
+
our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
require $GITWEB_CONFIG if -e $GITWEB_CONFIG;
@@ -76,8 +145,7 @@ if (! -d $git_temp) {
our $action = $cgi->param('a');
if (defined $action) {
if ($action =~ m/[^0-9a-zA-Z\.\-_]/) {
- undef $action;
- die_error(undef, "Invalid action parameter.");
+ die_error(undef, "Invalid action parameter");
}
# action which does not check rest of parameters
if ($action eq "opml") {
@@ -88,21 +156,20 @@ if (defined $action) {
our $project = ($cgi->param('p') || $ENV{'PATH_INFO'});
if (defined $project) {
- $project =~ s|^/||; $project =~ s|/$||;
- $project = validate_input($project);
- if (!defined($project)) {
- die_error(undef, "Invalid project parameter.");
+ $project =~ s|^/||;
+ $project =~ s|/$||;
+ $project = undef unless $project;
+}
+if (defined $project) {
+ if (!validate_input($project)) {
+ die_error(undef, "Invalid project parameter");
}
if (!(-d "$projectroot/$project")) {
- undef $project;
- die_error(undef, "No such directory.");
+ die_error(undef, "No such directory");
}
if (!(-e "$projectroot/$project/HEAD")) {
- undef $project;
- die_error(undef, "No such project.");
+ die_error(undef, "No such project");
}
- $rss_link = " ";
$ENV{'GIT_DIR'} = "$projectroot/$project";
} else {
git_project_list();
@@ -111,49 +178,50 @@ if (defined $project) {
our $file_name = $cgi->param('f');
if (defined $file_name) {
- $file_name = validate_input($file_name);
- if (!defined($file_name)) {
- die_error(undef, "Invalid file parameter.");
+ if (!validate_input($file_name)) {
+ die_error(undef, "Invalid file parameter");
+ }
+}
+
+our $file_parent = $cgi->param('fp');
+if (defined $file_parent) {
+ if (!validate_input($file_parent)) {
+ die_error(undef, "Invalid file parent parameter");
}
}
our $hash = $cgi->param('h');
if (defined $hash) {
- $hash = validate_input($hash);
- if (!defined($hash)) {
- die_error(undef, "Invalid hash parameter.");
+ if (!validate_input($hash)) {
+ die_error(undef, "Invalid hash parameter");
}
}
our $hash_parent = $cgi->param('hp');
if (defined $hash_parent) {
- $hash_parent = validate_input($hash_parent);
- if (!defined($hash_parent)) {
- die_error(undef, "Invalid hash parent parameter.");
+ if (!validate_input($hash_parent)) {
+ die_error(undef, "Invalid hash parent parameter");
}
}
our $hash_base = $cgi->param('hb');
if (defined $hash_base) {
- $hash_base = validate_input($hash_base);
- if (!defined($hash_base)) {
- die_error(undef, "Invalid hash base parameter.");
+ if (!validate_input($hash_base)) {
+ die_error(undef, "Invalid hash base parameter");
}
}
our $page = $cgi->param('pg');
if (defined $page) {
if ($page =~ m/[^0-9]$/) {
- undef $page;
- die_error(undef, "Invalid page parameter.");
+ die_error(undef, "Invalid page parameter");
}
}
our $searchtext = $cgi->param('s');
if (defined $searchtext) {
if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
- undef $searchtext;
- die_error(undef, "Invalid search parameter.");
+ die_error(undef, "Invalid search parameter");
}
$searchtext = quotemeta $searchtext;
}
@@ -178,16 +246,46 @@ my %actions = (
"tag" => \&git_tag,
"tags" => \&git_tags,
"tree" => \&git_tree,
+ "snapshot" => \&git_snapshot,
);
$action = 'summary' if (!defined($action));
if (!defined($actions{$action})) {
- undef $action;
- die_error(undef, "Unknown action.");
+ die_error(undef, "Unknown action");
}
$actions{$action}->();
exit;
+## ======================================================================
+## action links
+
+sub href(%) {
+ my %mapping = (
+ action => "a",
+ project => "p",
+ file_name => "f",
+ file_parent => "fp",
+ hash => "h",
+ hash_parent => "hp",
+ hash_base => "hb",
+ page => "pg",
+ searchtext => "s",
+ );
+
+ my %params = @_;
+ $params{"project"} ||= $project;
+
+ my $href = "$my_uri?";
+ $href .= esc_param( join(";",
+ map {
+ "$mapping{$_}=$params{$_}" if defined $params{$_}
+ } keys %params
+ ) );
+
+ return $href;
+}
+
+
## ======================================================================
## validation, quoting/unquoting and escaping
@@ -235,6 +333,20 @@ sub unquote {
return $str;
}
+# escape tabs (convert tabs to spaces)
+sub untabify {
+ my $line = shift;
+
+ while ((my $pos = index($line, "\t")) != -1) {
+ if (my $count = (8 - ($pos % 8))) {
+ my $spaces = ' ' x $count;
+ $line =~ s/\t/$spaces/;
+ }
+ }
+
+ return $line;
+}
+
## ----------------------------------------------------------------------
## HTML aware string manipulation
@@ -351,7 +463,7 @@ sub format_log_line_html {
if ($line =~ m/([0-9a-fA-F]{40})/) {
my $hash_text = $1;
if (git_get_type($hash_text) eq "commit") {
- my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text);
+ my $link = $cgi->a({-class => "text", -href => href(action=>"commit", hash=>$hash_text)}, $hash_text);
$line =~ s/$hash_text/$link/;
}
}
@@ -359,21 +471,53 @@ sub format_log_line_html {
}
# format marker of refs pointing to given object
-sub git_get_referencing {
+sub format_ref_marker {
my ($refs, $id) = @_;
+ my $markers = '';
if (defined $refs->{$id}) {
- return ' ' . esc_html($refs->{$id}) . ' ';
+ foreach my $ref (@{$refs->{$id}}) {
+ my ($type, $name) = qw();
+ # e.g. tags/v2.6.11 or heads/next
+ if ($ref =~ m!^(.*?)s?/(.*)$!) {
+ $type = $1;
+ $name = $2;
+ } else {
+ $type = "ref";
+ $name = $ref;
+ }
+
+ $markers .= " " . esc_html($name) . " ";
+ }
+ }
+
+ if ($markers) {
+ return ' '. $markers . ' ';
} else {
return "";
}
}
+# format, perhaps shortened and with markers, title line
+sub format_subject_html {
+ my ($long, $short, $href, $extra) = @_;
+ $extra = '' unless defined($extra);
+
+ if (length($short) < length($long)) {
+ return $cgi->a({-href => $href, -class => "list subject",
+ -title => $long},
+ esc_html($short) . $extra);
+ } else {
+ return $cgi->a({-href => $href, -class => "list subject"},
+ esc_html($long) . $extra);
+ }
+}
+
## ----------------------------------------------------------------------
## git utility subroutines, invoking git commands
# get HEAD ref of given project as hash
-sub git_read_head {
+sub git_get_head_hash {
my $project = shift;
my $oENV = $ENV{'GIT_DIR'};
my $retval = undef;
@@ -403,24 +547,21 @@ sub git_get_type {
}
sub git_get_project_config {
- my $key = shift;
+ my ($key, $type) = @_;
return unless ($key);
$key =~ s/^gitweb\.//;
return if ($key =~ m/\W/);
- my $val = qx($GIT repo-config --get gitweb.$key);
+ my @x = ($GIT, 'repo-config');
+ if (defined $type) { push @x, $type; }
+ push @x, "--get";
+ push @x, "gitweb.$key";
+ my $val = qx(@x);
+ chomp $val;
return ($val);
}
-sub git_get_project_config_bool {
- my $val = git_get_project_config (@_);
- if ($val and $val =~ m/true|yes|on/) {
- return (1);
- }
- return; # implicit false
-}
-
# get hash of given path at given ref
sub git_get_hash_by_path {
my $base = shift;
@@ -429,7 +570,7 @@ sub git_get_hash_by_path {
my $tree = $base;
open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path
- or die_error(undef, "Open git-ls-tree failed.");
+ or die_error(undef, "Open git-ls-tree failed");
my $line = <$fd>;
close $fd or return undef;
@@ -442,7 +583,7 @@ sub git_get_hash_by_path {
## git utility functions, directly accessing git repository
# assumes that PATH is not symref
-sub git_read_hash {
+sub git_get_hash_by_ref {
my $path = shift;
open my $fd, "$projectroot/$path" or return undef;
@@ -454,7 +595,7 @@ sub git_read_hash {
}
}
-sub git_read_description {
+sub git_get_project_description {
my $path = shift;
open my $fd, "$projectroot/$path/description" or return undef;
@@ -464,7 +605,17 @@ sub git_read_description {
return $descr;
}
-sub git_read_projects {
+sub git_get_project_url_list {
+ my $path = shift;
+
+ open my $fd, "$projectroot/$path/cloneurl" or return undef;
+ my @git_project_url_list = map { chomp; $_ } <$fd>;
+ close $fd;
+
+ return wantarray ? @git_project_url_list : \@git_project_url_list;
+}
+
+sub git_get_projects_list {
my @list;
if (-d $projects_list) {
@@ -508,21 +659,58 @@ sub git_read_projects {
return @list;
}
-sub read_info_ref {
+sub git_get_project_owner {
+ my $project = shift;
+ my $owner;
+
+ return undef unless $project;
+
+ # read from file (url-encoded):
+ # 'git%2Fgit.git Linus+Torvalds'
+ # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
+ # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
+ if (-f $projects_list) {
+ open (my $fd , $projects_list);
+ while (my $line = <$fd>) {
+ chomp $line;
+ my ($pr, $ow) = split ' ', $line;
+ $pr = unescape($pr);
+ $ow = unescape($ow);
+ if ($pr eq $project) {
+ $owner = decode("utf8", $ow, Encode::FB_DEFAULT);
+ last;
+ }
+ }
+ close $fd;
+ }
+ if (!defined $owner) {
+ $owner = get_file_owner("$projectroot/$project");
+ }
+
+ return $owner;
+}
+
+sub git_get_references {
my $type = shift || "";
my %refs;
+ my $fd;
# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
# c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
- open my $fd, "$projectroot/$project/info/refs" or return;
+ if (-f "$projectroot/$project/info/refs") {
+ open $fd, "$projectroot/$project/info/refs"
+ or return;
+ } else {
+ open $fd, "-|", $GIT, "ls-remote", "."
+ or return;
+ }
+
while (my $line = <$fd>) {
chomp $line;
- # attention: for $type == "" it saves only last path part of ref name
- # e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb'
- if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) {
+ if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) {
if (defined $refs{$1}) {
- $refs{$1} .= " / $2";
+ push @{$refs{$1}}, $2;
} else {
- $refs{$1} = $2;
+ $refs{$1} = [ $2 ];
}
}
}
@@ -533,7 +721,7 @@ sub read_info_ref {
## ----------------------------------------------------------------------
## parse to hash functions
-sub date_str {
+sub parse_date {
my $epoch = shift;
my $tz = shift || "-0000";
@@ -558,7 +746,7 @@ sub date_str {
return %date;
}
-sub git_read_tag {
+sub parse_tag {
my $tag_id = shift;
my %tag;
my @comment;
@@ -593,7 +781,7 @@ sub git_read_tag {
return %tag
}
-sub git_read_commit {
+sub parse_commit {
my $commit_id = shift;
my $commit_text = shift;
@@ -687,10 +875,81 @@ sub git_read_commit {
return %co;
}
+# parse ref from ref_file, given by ref_id, with given type
+sub parse_ref {
+ my $ref_file = shift;
+ my $ref_id = shift;
+ my $type = shift || git_get_type($ref_id);
+ my %ref_item;
+
+ $ref_item{'type'} = $type;
+ $ref_item{'id'} = $ref_id;
+ $ref_item{'epoch'} = 0;
+ $ref_item{'age'} = "unknown";
+ if ($type eq "tag") {
+ my %tag = parse_tag($ref_id);
+ $ref_item{'comment'} = $tag{'comment'};
+ if ($tag{'type'} eq "commit") {
+ my %co = parse_commit($tag{'object'});
+ $ref_item{'epoch'} = $co{'committer_epoch'};
+ $ref_item{'age'} = $co{'age_string'};
+ } elsif (defined($tag{'epoch'})) {
+ my $age = time - $tag{'epoch'};
+ $ref_item{'epoch'} = $tag{'epoch'};
+ $ref_item{'age'} = age_string($age);
+ }
+ $ref_item{'reftype'} = $tag{'type'};
+ $ref_item{'name'} = $tag{'name'};
+ $ref_item{'refid'} = $tag{'object'};
+ } elsif ($type eq "commit"){
+ my %co = parse_commit($ref_id);
+ $ref_item{'reftype'} = "commit";
+ $ref_item{'name'} = $ref_file;
+ $ref_item{'title'} = $co{'title'};
+ $ref_item{'refid'} = $ref_id;
+ $ref_item{'epoch'} = $co{'committer_epoch'};
+ $ref_item{'age'} = $co{'age_string'};
+ } else {
+ $ref_item{'reftype'} = $type;
+ $ref_item{'name'} = $ref_file;
+ $ref_item{'refid'} = $ref_id;
+ }
+
+ return %ref_item;
+}
+
+# parse line of git-diff-tree "raw" output
+sub parse_difftree_raw_line {
+ my $line = shift;
+ my %res;
+
+ # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
+ # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
+ if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
+ $res{'from_mode'} = $1;
+ $res{'to_mode'} = $2;
+ $res{'from_id'} = $3;
+ $res{'to_id'} = $4;
+ $res{'status'} = $5;
+ $res{'similarity'} = $6;
+ if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
+ ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
+ } else {
+ $res{'file'} = unquote($7);
+ }
+ }
+ # 'c512b523472485aef4fff9e57b229d9d243c967f'
+ #elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
+ # $res{'commit'} = $1;
+ #}
+
+ return wantarray ? %res : \%res;
+}
+
## ......................................................................
## parse to array of hashes functions
-sub git_read_refs {
+sub git_get_refs_list {
my $ref_dir = shift;
my @reflist;
@@ -704,46 +963,13 @@ sub git_read_refs {
}, "$projectroot/$project/$ref_dir");
foreach my $ref_file (@refs) {
- my $ref_id = git_read_hash("$project/$ref_dir/$ref_file");
+ my $ref_id = git_get_hash_by_ref("$project/$ref_dir/$ref_file");
my $type = git_get_type($ref_id) || next;
- my %ref_item;
- my %co;
- $ref_item{'type'} = $type;
- $ref_item{'id'} = $ref_id;
- $ref_item{'epoch'} = 0;
- $ref_item{'age'} = "unknown";
- if ($type eq "tag") {
- my %tag = git_read_tag($ref_id);
- $ref_item{'comment'} = $tag{'comment'};
- if ($tag{'type'} eq "commit") {
- %co = git_read_commit($tag{'object'});
- $ref_item{'epoch'} = $co{'committer_epoch'};
- $ref_item{'age'} = $co{'age_string'};
- } elsif (defined($tag{'epoch'})) {
- my $age = time - $tag{'epoch'};
- $ref_item{'epoch'} = $tag{'epoch'};
- $ref_item{'age'} = age_string($age);
- }
- $ref_item{'reftype'} = $tag{'type'};
- $ref_item{'name'} = $tag{'name'};
- $ref_item{'refid'} = $tag{'object'};
- } elsif ($type eq "commit"){
- %co = git_read_commit($ref_id);
- $ref_item{'reftype'} = "commit";
- $ref_item{'name'} = $ref_file;
- $ref_item{'title'} = $co{'title'};
- $ref_item{'refid'} = $ref_id;
- $ref_item{'epoch'} = $co{'committer_epoch'};
- $ref_item{'age'} = $co{'age_string'};
- } else {
- $ref_item{'reftype'} = $type;
- $ref_item{'name'} = $ref_file;
- $ref_item{'refid'} = $ref_id;
- }
+ my %ref_item = parse_ref($ref_file, $ref_id, $type);
push @reflist, \%ref_item;
}
- # sort tags by age
+ # sort refs by age
@reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist;
return \@reflist;
}
@@ -775,6 +1001,7 @@ sub mimetype_guess_file {
my %mimemap;
open(MIME, $mimemap) or return undef;
while () {
+ next if m/^#/; # skip comments
my ($mime, $exts) = split(/\t+/);
if (defined $exts) {
my @exts = split(/\s+/, $exts);
@@ -796,14 +1023,17 @@ sub mimetype_guess {
if ($mimetypes_file) {
my $file = $mimetypes_file;
- #$file =~ m#^/# or $file = "$projectroot/$path/$file";
+ if ($file !~ m!^/!) { # if it is relative path
+ # it is relative to project
+ $file = "$projectroot/$project/$file";
+ }
$mime = mimetype_guess_file($filename, $file);
}
$mime ||= mimetype_guess_file($filename, '/etc/mime.types');
return $mime;
}
-sub git_blob_plain_mimetype {
+sub blob_mimetype {
my $fd = shift;
my $filename = shift;
@@ -856,7 +1086,7 @@ sub git_header_html {
# 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
# we have to do this because MSIE sometimes globs '*/*', pretending to
# support xhtml+xml but choking when it gets what it asked for.
- if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
+ if (defined $cgi->http('HTTP_ACCEPT') && $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
$content_type = 'application/xhtml+xml';
} else {
$content_type = 'text/html';
@@ -866,24 +1096,30 @@ sub git_header_html {
-
+
+
$title
-$rss_link
-
-
EOF
- print "