diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi index 0953b8cd56..16c368155b 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.cgi @@ -160,21 +160,7 @@ if (defined $searchtext) { $searchtext = quotemeta $searchtext; } -sub validate_input { - my $input = shift; - - if ($input =~ m/^[0-9a-fA-F]{40}$/) { - return $input; - } - if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) { - return undef; - } - if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) { - return undef; - } - return $input; -} - +# dispatch if (!defined $action || $action eq "summary") { git_summary(); exit; @@ -235,6 +221,24 @@ if (!defined $action || $action eq "summary") { exit; } +## ====================================================================== +## validation, quoting/unquoting and escaping + +sub validate_input { + my $input = shift; + + if ($input =~ m/^[0-9a-fA-F]{40}$/) { + return $input; + } + if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) { + return undef; + } + if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) { + return undef; + } + return $input; +} + # quote unsafe chars, but keep the slash, even when it's not # correct, but quoted slashes look too horrible in bookmarks sub esc_param { @@ -264,6 +268,29 @@ sub unquote { return $str; } +## ---------------------------------------------------------------------- +## HTML aware string manipulation + +sub chop_str { + my $str = shift; + my $len = shift; + my $add_len = shift || 10; + + # allow only $len chars, but don't cut a word if it would fit in $add_len + # if it doesn't fit, cut it if it's still longer than the dots we would add + $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; + my $body = $1; + my $tail = $2; + if (length($tail) > 4) { + $tail = " ..."; + $body =~ s/&[^;]*$//; # remove chopped character entities + } + return "$body$tail"; +} + +## ---------------------------------------------------------------------- +## functions returning short strings + # CSS class for given age value (in seconds) sub age_class { my $age = shift; @@ -277,6 +304,576 @@ sub age_class { } } +# convert age in seconds to "nn units ago" string +sub age_string { + my $age = shift; + my $age_str; + + if ($age > 60*60*24*365*2) { + $age_str = (int $age/60/60/24/365); + $age_str .= " years ago"; + } elsif ($age > 60*60*24*(365/12)*2) { + $age_str = int $age/60/60/24/(365/12); + $age_str .= " months ago"; + } elsif ($age > 60*60*24*7*2) { + $age_str = int $age/60/60/24/7; + $age_str .= " weeks ago"; + } elsif ($age > 60*60*24*2) { + $age_str = int $age/60/60/24; + $age_str .= " days ago"; + } elsif ($age > 60*60*2) { + $age_str = int $age/60/60; + $age_str .= " hours ago"; + } elsif ($age > 60*2) { + $age_str = int $age/60; + $age_str .= " min ago"; + } elsif ($age > 2) { + $age_str = int $age; + $age_str .= " sec ago"; + } else { + $age_str .= " right now"; + } + return $age_str; +} + +# convert file mode in octal to symbolic file mode string +sub mode_str { + my $mode = oct shift; + + if (S_ISDIR($mode & S_IFMT)) { + return 'drwxr-xr-x'; + } elsif (S_ISLNK($mode)) { + return 'lrwxrwxrwx'; + } elsif (S_ISREG($mode)) { + # git cares only about the executable bit + if ($mode & S_IXUSR) { + return '-rwxr-xr-x'; + } else { + return '-rw-r--r--'; + }; + } else { + return '----------'; + } +} + +# convert file mode in octal to file type string +sub file_type { + my $mode = oct shift; + + if (S_ISDIR($mode & S_IFMT)) { + return "directory"; + } elsif (S_ISLNK($mode)) { + return "symlink"; + } elsif (S_ISREG($mode)) { + return "file"; + } else { + return "unknown"; + } +} + +## ---------------------------------------------------------------------- +## functions returning short HTML fragments, or transforming HTML fragments +## which don't beling to other sections + +# format line of commit message or tag comment +sub format_log_line_html { + my $line = shift; + + $line = esc_html($line); + $line =~ s/ / /g; + 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); + $line =~ s/$hash_text/$link/; + } + } + return $line; +} + +# format marker of refs pointing to given object +sub git_get_referencing { + my ($refs, $id) = @_; + + if (defined $refs->{$id}) { + return ' ' . esc_html($refs->{$id}) . ''; + } else { + return ""; + } +} + +## ---------------------------------------------------------------------- +## git utility subroutines, invoking git commands + +# get HEAD ref of given project as hash +sub git_read_head { + my $project = shift; + my $oENV = $ENV{'GIT_DIR'}; + my $retval = undef; + $ENV{'GIT_DIR'} = "$projectroot/$project"; + if (open my $fd, "-|", $GIT, "rev-parse", "--verify", "HEAD") { + my $head = <$fd>; + close $fd; + if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) { + $retval = $1; + } + } + if (defined $oENV) { + $ENV{'GIT_DIR'} = $oENV; + } + return $retval; +} + +# get type of given object +sub git_get_type { + my $hash = shift; + + open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return; + my $type = <$fd>; + close $fd or return; + chomp $type; + return $type; +} + +sub git_get_project_config { + my $key = shift; + + return unless ($key); + $key =~ s/^gitweb\.//; + return if ($key =~ m/\W/); + + my $val = qx($GIT repo-config --get gitweb.$key); + 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; + my $path = shift || return undef; + + my $tree = $base; + + open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path + or die_error(undef, "Open git-ls-tree failed."); + my $line = <$fd>; + close $fd or return undef; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + return $3; +} + +## ...................................................................... +## git utility functions, directly accessing git repository + +# assumes that PATH is not symref +sub git_read_hash { + my $path = shift; + + open my $fd, "$projectroot/$path" or return undef; + my $head = <$fd>; + close $fd; + chomp $head; + if ($head =~ m/^[0-9a-fA-F]{40}$/) { + return $head; + } +} + +sub git_read_description { + my $path = shift; + + open my $fd, "$projectroot/$path/description" or return undef; + my $descr = <$fd>; + close $fd; + chomp $descr; + return $descr; +} + +sub git_read_projects { + my @list; + + if (-d $projects_list) { + # search in directory + my $dir = $projects_list; + opendir my ($dh), $dir or return undef; + while (my $dir = readdir($dh)) { + if (-e "$projectroot/$dir/HEAD") { + my $pr = { + path => $dir, + }; + push @list, $pr + } + } + closedir($dh); + } elsif (-f $projects_list) { + # read from file(url-encoded): + # 'git%2Fgit.git Linus+Torvalds' + # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' + # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' + open my ($fd), $projects_list or return undef; + while (my $line = <$fd>) { + chomp $line; + my ($path, $owner) = split ' ', $line; + $path = unescape($path); + $owner = unescape($owner); + if (!defined $path) { + next; + } + if (-e "$projectroot/$path/HEAD") { + my $pr = { + path => $path, + owner => decode("utf8", $owner, Encode::FB_DEFAULT), + }; + push @list, $pr + } + } + close $fd; + } + @list = sort {$a->{'path'} cmp $b->{'path'}} @list; + return @list; +} + +sub read_info_ref { + my $type = shift || ""; + my %refs; + # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 + # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} + open my $fd, "$projectroot/$project/info/refs" 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 (defined $refs{$1}) { + $refs{$1} .= " / $2"; + } else { + $refs{$1} = $2; + } + } + } + close $fd or return; + return \%refs; +} + +## ---------------------------------------------------------------------- +## parse to hash functions + +sub date_str { + my $epoch = shift; + my $tz = shift || "-0000"; + + my %date; + my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); + my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); + $date{'hour'} = $hour; + $date{'minute'} = $min; + $date{'mday'} = $mday; + $date{'day'} = $days[$wday]; + $date{'month'} = $months[$mon]; + $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; + $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; + + $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; + my $local = $epoch + ((int $1 + ($2/60)) * 3600); + ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); + $date{'hour_local'} = $hour; + $date{'minute_local'} = $min; + $date{'tz_local'} = $tz; + return %date; +} + +sub git_read_tag { + my $tag_id = shift; + my %tag; + my @comment; + + open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return; + $tag{'id'} = $tag_id; + while (my $line = <$fd>) { + chomp $line; + if ($line =~ m/^object ([0-9a-fA-F]{40})$/) { + $tag{'object'} = $1; + } elsif ($line =~ m/^type (.+)$/) { + $tag{'type'} = $1; + } elsif ($line =~ m/^tag (.+)$/) { + $tag{'name'} = $1; + } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) { + $tag{'author'} = $1; + $tag{'epoch'} = $2; + $tag{'tz'} = $3; + } elsif ($line =~ m/--BEGIN/) { + push @comment, $line; + last; + } elsif ($line eq "") { + last; + } + } + push @comment, <$fd>; + $tag{'comment'} = \@comment; + close $fd or return; + if (!defined $tag{'name'}) { + return + }; + return %tag +} + +sub git_read_commit { + my $commit_id = shift; + my $commit_text = shift; + + my @commit_lines; + my %co; + + if (defined $commit_text) { + @commit_lines = @$commit_text; + } else { + $/ = "\0"; + open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return; + @commit_lines = split '\n', <$fd>; + close $fd or return; + $/ = "\n"; + pop @commit_lines; + } + my $header = shift @commit_lines; + if (!($header =~ m/^[0-9a-fA-F]{40}/)) { + return; + } + ($co{'id'}, my @parents) = split ' ', $header; + $co{'parents'} = \@parents; + $co{'parent'} = $parents[0]; + while (my $line = shift @commit_lines) { + last if $line eq "\n"; + if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { + $co{'tree'} = $1; + } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { + $co{'author'} = $1; + $co{'author_epoch'} = $2; + $co{'author_tz'} = $3; + if ($co{'author'} =~ m/^([^<]+) 50) { + $title =~ s/^Automatic //; + $title =~ s/^merge (of|with) /Merge ... /i; + if (length($title) > 50) { + $title =~ s/(http|rsync):\/\///; + } + if (length($title) > 50) { + $title =~ s/(master|www|rsync)\.//; + } + if (length($title) > 50) { + $title =~ s/kernel.org:?//; + } + if (length($title) > 50) { + $title =~ s/\/pub\/scm//; + } + } + $co{'title_short'} = chop_str($title, 50, 5); + last; + } + } + # remove added spaces + foreach my $line (@commit_lines) { + $line =~ s/^ //; + } + $co{'comment'} = \@commit_lines; + + my $age = time - $co{'committer_epoch'}; + $co{'age'} = $age; + $co{'age_string'} = age_string($age); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'}); + if ($age > 60*60*24*7*2) { + $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; + $co{'age_string_age'} = $co{'age_string'}; + } else { + $co{'age_string_date'} = $co{'age_string'}; + $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; + } + return %co; +} + +## ...................................................................... +## parse to array of hashes functions + +sub git_read_refs { + my $ref_dir = shift; + my @reflist; + + my @refs; + opendir my $dh, "$projectroot/$project/$ref_dir"; + while (my $dir = readdir($dh)) { + if ($dir =~ m/^\./) { + next; + } + if (-d "$projectroot/$project/$ref_dir/$dir") { + opendir my $dh2, "$projectroot/$project/$ref_dir/$dir"; + my @subdirs = grep !m/^\./, readdir $dh2; + closedir($dh2); + foreach my $subdir (@subdirs) { + push @refs, "$dir/$subdir" + } + next; + } + push @refs, $dir; + } + closedir($dh); + foreach my $ref_file (@refs) { + my $ref_id = git_read_hash("$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; + } + + push @reflist, \%ref_item; + } + # sort tags by age + @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; + return \@reflist; +} + +## ---------------------------------------------------------------------- +## filesystem-related functions + +sub get_file_owner { + my $path = shift; + + my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path); + my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid); + if (!defined $gcos) { + return undef; + } + my $owner = $gcos; + $owner =~ s/[,;].*$//; + return decode("utf8", $owner, Encode::FB_DEFAULT); +} + +## ...................................................................... +## mimetype related functions + +sub mimetype_guess_file { + my $filename = shift; + my $mimemap = shift; + -r $mimemap or return undef; + + my %mimemap; + open(MIME, $mimemap) or return undef; + while () { + my ($mime, $exts) = split(/\t+/); + my @exts = split(/\s+/, $exts); + foreach my $ext (@exts) { + $mimemap{$ext} = $mime; + } + } + close(MIME); + + $filename =~ /\.(.*?)$/; + return $mimemap{$1}; +} + +sub mimetype_guess { + my $filename = shift; + my $mime; + $filename =~ /\./ or return undef; + + if ($mimetypes_file) { + my $file = $mimetypes_file; + #$file =~ m#^/# or $file = "$projectroot/$path/$file"; + $mime = mimetype_guess_file($filename, $file); + } + $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); + return $mime; +} + +sub git_blob_plain_mimetype { + my $fd = shift; + my $filename = shift; + + if ($filename) { + my $mime = mimetype_guess($filename); + $mime and return $mime; + } + + # just in case + return $default_blob_plain_mimetype unless $fd; + + if (-T $fd) { + return 'text/plain' . + ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); + } elsif (! $filename) { + return 'application/octet-stream'; + } elsif ($filename =~ m/\.png$/i) { + return 'image/png'; + } elsif ($filename =~ m/\.gif$/i) { + return 'image/gif'; + } elsif ($filename =~ m/\.jpe?g$/i) { + return 'image/jpeg'; + } else { + return 'application/octet-stream'; + } +} + +## ====================================================================== +## functions printing HTML: header, footer, error page + sub git_header_html { my $status = shift || "200 OK"; my $expires = shift; @@ -386,6 +983,9 @@ sub die_error { exit; } +## ---------------------------------------------------------------------- +## functions printing or outputting HTML: navigation + sub git_page_nav { my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; $extra = '' if !defined $extra; # pager or formats @@ -420,19 +1020,6 @@ sub git_page_nav { "\n"; } -sub git_header_div { - my ($action, $title, $hash, $hash_base) = @_; - my $rest = ''; - - $rest .= ";h=$hash" if $hash; - $rest .= ";hb=$hash_base" if $hash_base; - - print "
\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"), - -class => "title"}, $title ? $title : $action) . "\n" . - "
\n"; -} - sub git_get_paging_nav { my ($action, $hash, $head, $page, $nrevs) = @_; my $paging_nav; @@ -463,657 +1050,38 @@ sub git_get_paging_nav { return $paging_nav; } -sub git_get_type { - my $hash = shift; +## ...................................................................... +## functions printing or outputting HTML: div - open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return; - my $type = <$fd>; - close $fd or return; - chomp $type; - return $type; +sub git_header_div { + my ($action, $title, $hash, $hash_base) = @_; + my $rest = ''; + + $rest .= ";h=$hash" if $hash; + $rest .= ";hb=$hash_base" if $hash_base; + + print "
\n" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"), + -class => "title"}, $title ? $title : $action) . "\n" . + "
\n"; } -sub git_read_head { - my $project = shift; - my $oENV = $ENV{'GIT_DIR'}; - my $retval = undef; - $ENV{'GIT_DIR'} = "$projectroot/$project"; - if (open my $fd, "-|", $GIT, "rev-parse", "--verify", "HEAD") { - my $head = <$fd>; - close $fd; - if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) { - $retval = $1; - } - } - if (defined $oENV) { - $ENV{'GIT_DIR'} = $oENV; - } - return $retval; -} +sub git_print_page_path { + my $name = shift; + my $type = shift; -sub git_read_hash { - my $path = shift; - - open my $fd, "$projectroot/$path" or return undef; - my $head = <$fd>; - close $fd; - chomp $head; - if ($head =~ m/^[0-9a-fA-F]{40}$/) { - return $head; - } -} - -sub git_read_description { - my $path = shift; - - open my $fd, "$projectroot/$path/description" or return undef; - my $descr = <$fd>; - close $fd; - chomp $descr; - return $descr; -} - -sub git_read_tag { - my $tag_id = shift; - my %tag; - my @comment; - - open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return; - $tag{'id'} = $tag_id; - while (my $line = <$fd>) { - chomp $line; - if ($line =~ m/^object ([0-9a-fA-F]{40})$/) { - $tag{'object'} = $1; - } elsif ($line =~ m/^type (.+)$/) { - $tag{'type'} = $1; - } elsif ($line =~ m/^tag (.+)$/) { - $tag{'name'} = $1; - } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) { - $tag{'author'} = $1; - $tag{'epoch'} = $2; - $tag{'tz'} = $3; - } elsif ($line =~ m/--BEGIN/) { - push @comment, $line; - last; - } elsif ($line eq "") { - last; - } - } - push @comment, <$fd>; - $tag{'comment'} = \@comment; - close $fd or return; - if (!defined $tag{'name'}) { - return - }; - return %tag -} - -sub age_string { - my $age = shift; - my $age_str; - - if ($age > 60*60*24*365*2) { - $age_str = (int $age/60/60/24/365); - $age_str .= " years ago"; - } elsif ($age > 60*60*24*(365/12)*2) { - $age_str = int $age/60/60/24/(365/12); - $age_str .= " months ago"; - } elsif ($age > 60*60*24*7*2) { - $age_str = int $age/60/60/24/7; - $age_str .= " weeks ago"; - } elsif ($age > 60*60*24*2) { - $age_str = int $age/60/60/24; - $age_str .= " days ago"; - } elsif ($age > 60*60*2) { - $age_str = int $age/60/60; - $age_str .= " hours ago"; - } elsif ($age > 60*2) { - $age_str = int $age/60; - $age_str .= " min ago"; - } elsif ($age > 2) { - $age_str = int $age; - $age_str .= " sec ago"; + if (!defined $name) { + print "
/
\n"; + } elsif ($type =~ "blob") { + print "
" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "
\n"; } else { - $age_str .= " right now"; - } - return $age_str; -} - -sub git_read_commit { - my $commit_id = shift; - my $commit_text = shift; - - my @commit_lines; - my %co; - - if (defined $commit_text) { - @commit_lines = @$commit_text; - } else { - $/ = "\0"; - open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return; - @commit_lines = split '\n', <$fd>; - close $fd or return; - $/ = "\n"; - pop @commit_lines; - } - my $header = shift @commit_lines; - if (!($header =~ m/^[0-9a-fA-F]{40}/)) { - return; - } - ($co{'id'}, my @parents) = split ' ', $header; - $co{'parents'} = \@parents; - $co{'parent'} = $parents[0]; - while (my $line = shift @commit_lines) { - last if $line eq "\n"; - if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { - $co{'tree'} = $1; - } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { - $co{'author'} = $1; - $co{'author_epoch'} = $2; - $co{'author_tz'} = $3; - if ($co{'author'} =~ m/^([^<]+) 50) { - $title =~ s/^Automatic //; - $title =~ s/^merge (of|with) /Merge ... /i; - if (length($title) > 50) { - $title =~ s/(http|rsync):\/\///; - } - if (length($title) > 50) { - $title =~ s/(master|www|rsync)\.//; - } - if (length($title) > 50) { - $title =~ s/kernel.org:?//; - } - if (length($title) > 50) { - $title =~ s/\/pub\/scm//; - } - } - $co{'title_short'} = chop_str($title, 50, 5); - last; - } - } - # remove added spaces - foreach my $line (@commit_lines) { - $line =~ s/^ //; - } - $co{'comment'} = \@commit_lines; - - my $age = time - $co{'committer_epoch'}; - $co{'age'} = $age; - $co{'age_string'} = age_string($age); - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'}); - if ($age > 60*60*24*7*2) { - $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; - $co{'age_string_age'} = $co{'age_string'}; - } else { - $co{'age_string_date'} = $co{'age_string'}; - $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; - } - return %co; -} - -sub git_diff_print { - my $from = shift; - my $from_name = shift; - my $to = shift; - my $to_name = shift; - my $format = shift || "html"; - - my $from_tmp = "/dev/null"; - my $to_tmp = "/dev/null"; - my $pid = $$; - - # create tmp from-file - if (defined $from) { - $from_tmp = "$git_temp/gitweb_" . $$ . "_from"; - open my $fd2, "> $from_tmp"; - open my $fd, "-|", $GIT, "cat-file", "blob", $from; - my @file = <$fd>; - print $fd2 @file; - close $fd2; - close $fd; - } - - # create tmp to-file - if (defined $to) { - $to_tmp = "$git_temp/gitweb_" . $$ . "_to"; - open my $fd2, "> $to_tmp"; - open my $fd, "-|", $GIT, "cat-file", "blob", $to; - my @file = <$fd>; - print $fd2 @file; - close $fd2; - close $fd; - } - - open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp"; - if ($format eq "plain") { - undef $/; - print <$fd>; - $/ = "\n"; - } else { - while (my $line = <$fd>) { - chomp $line; - my $char = substr($line, 0, 1); - my $diff_class = ""; - if ($char eq '+') { - $diff_class = " add"; - } elsif ($char eq "-") { - $diff_class = " rem"; - } elsif ($char eq "@") { - $diff_class = " chunk_header"; - } elsif ($char eq "\\") { - # skip errors - next; - } - while ((my $pos = index($line, "\t")) != -1) { - if (my $count = (8 - (($pos-1) % 8))) { - my $spaces = ' ' x $count; - $line =~ s/\t/$spaces/; - } - } - print "
" . esc_html($line) . "
\n"; - } - } - close $fd; - - if (defined $from) { - unlink($from_tmp); - } - if (defined $to) { - unlink($to_tmp); + print "
" . esc_html($name) . "
\n"; } } -sub mode_str { - my $mode = oct shift; - - if (S_ISDIR($mode & S_IFMT)) { - return 'drwxr-xr-x'; - } elsif (S_ISLNK($mode)) { - return 'lrwxrwxrwx'; - } elsif (S_ISREG($mode)) { - # git cares only about the executable bit - if ($mode & S_IXUSR) { - return '-rwxr-xr-x'; - } else { - return '-rw-r--r--'; - }; - } else { - return '----------'; - } -} - -sub chop_str { - my $str = shift; - my $len = shift; - my $add_len = shift || 10; - - # allow only $len chars, but don't cut a word if it would fit in $add_len - # if it doesn't fit, cut it if it's still longer than the dots we would add - $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; - my $body = $1; - my $tail = $2; - if (length($tail) > 4) { - $tail = " ..."; - $body =~ s/&[^;]*$//; # remove chopped character entities - } - return "$body$tail"; -} - -sub file_type { - my $mode = oct shift; - - if (S_ISDIR($mode & S_IFMT)) { - return "directory"; - } elsif (S_ISLNK($mode)) { - return "symlink"; - } elsif (S_ISREG($mode)) { - return "file"; - } else { - return "unknown"; - } -} - -sub format_log_line_html { - my $line = shift; - - $line = esc_html($line); - $line =~ s/ / /g; - 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); - $line =~ s/$hash_text/$link/; - } - } - return $line; -} - -sub date_str { - my $epoch = shift; - my $tz = shift || "-0000"; - - my %date; - my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); - my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); - $date{'hour'} = $hour; - $date{'minute'} = $min; - $date{'mday'} = $mday; - $date{'day'} = $days[$wday]; - $date{'month'} = $months[$mon]; - $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; - $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; - - $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; - my $local = $epoch + ((int $1 + ($2/60)) * 3600); - ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); - $date{'hour_local'} = $hour; - $date{'minute_local'} = $min; - $date{'tz_local'} = $tz; - return %date; -} - -# git-logo (cached in browser for one day) -sub git_logo { - binmode STDOUT, ':raw'; - print $cgi->header(-type => 'image/png', -expires => '+1d'); - # cat git-logo.png | hexdump -e '16/1 " %02x" "\n"' | sed 's/ /\\x/g' - print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" . - "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" . - "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" . - "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" . - "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" . - "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" . - "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" . - "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" . - "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" . - "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" . - "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" . - "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" . - "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; -} - -sub get_file_owner { - my $path = shift; - - my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path); - my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid); - if (!defined $gcos) { - return undef; - } - my $owner = $gcos; - $owner =~ s/[,;].*$//; - return decode("utf8", $owner, Encode::FB_DEFAULT); -} - -sub git_read_projects { - my @list; - - if (-d $projects_list) { - # search in directory - my $dir = $projects_list; - opendir my ($dh), $dir or return undef; - while (my $dir = readdir($dh)) { - if (-e "$projectroot/$dir/HEAD") { - my $pr = { - path => $dir, - }; - push @list, $pr - } - } - closedir($dh); - } elsif (-f $projects_list) { - # read from file(url-encoded): - # 'git%2Fgit.git Linus+Torvalds' - # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' - # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' - open my ($fd), $projects_list or return undef; - while (my $line = <$fd>) { - chomp $line; - my ($path, $owner) = split ' ', $line; - $path = unescape($path); - $owner = unescape($owner); - if (!defined $path) { - next; - } - if (-e "$projectroot/$path/HEAD") { - my $pr = { - path => $path, - owner => decode("utf8", $owner, Encode::FB_DEFAULT), - }; - push @list, $pr - } - } - close $fd; - } - @list = sort {$a->{'path'} cmp $b->{'path'}} @list; - return @list; -} - -sub git_get_project_config { - my $key = shift; - - return unless ($key); - $key =~ s/^gitweb\.//; - return if ($key =~ m/\W/); - - my $val = qx($GIT repo-config --get gitweb.$key); - 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 -} - -sub git_project_list { - my @list = git_read_projects(); - my @projects; - if (!@list) { - die_error(undef, "No project found."); - } - foreach my $pr (@list) { - my $head = git_read_head($pr->{'path'}); - if (!defined $head) { - next; - } - $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}"; - my %co = git_read_commit($head); - if (!%co) { - next; - } - $pr->{'commit'} = \%co; - if (!defined $pr->{'descr'}) { - my $descr = git_read_description($pr->{'path'}) || ""; - $pr->{'descr'} = chop_str($descr, 25, 5); - } - if (!defined $pr->{'owner'}) { - $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; - } - push @projects, $pr; - } - git_header_html(); - if (-f $home_text) { - print "
\n"; - open (my $fd, $home_text); - print <$fd>; - close $fd; - print "
\n"; - } - print "\n" . - "\n"; - if (!defined($order) || (defined($order) && ($order eq "project"))) { - @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; - print "\n"; - } else { - print "\n"; - } - if (defined($order) && ($order eq "descr")) { - @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; - print "\n"; - } else { - print "\n"; - } - if (defined($order) && ($order eq "owner")) { - @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; - print "\n"; - } else { - print "\n"; - } - if (defined($order) && ($order eq "age")) { - @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; - print "\n"; - } else { - print "\n"; - } - print "\n" . - "\n"; - my $alternate = 0; - foreach my $pr (@projects) { - if ($alternate) { - print "\n"; - } else { - print "\n"; - } - $alternate ^= 1; - print "\n" . - "\n" . - "\n"; - print "\n" . - "\n" . - "\n"; - } - print "
Project" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "Description" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "Owner" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "Last Change" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "" . esc_html($pr->{'descr'}) . "" . chop_str($pr->{'owner'}, 15) . "{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . - "
\n"; - git_footer_html(); -} - -sub read_info_ref { - my $type = shift || ""; - my %refs; - # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 - # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} - open my $fd, "$projectroot/$project/info/refs" 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 (defined $refs{$1}) { - $refs{$1} .= " / $2"; - } else { - $refs{$1} = $2; - } - } - } - close $fd or return; - return \%refs; -} - -sub git_get_referencing { - my ($refs, $id) = @_; - - if (defined $refs->{$id}) { - return ' ' . esc_html($refs->{$id}) . ''; - } else { - return ""; - } -} - -sub git_read_refs { - my $ref_dir = shift; - my @reflist; - - my @refs; - opendir my $dh, "$projectroot/$project/$ref_dir"; - while (my $dir = readdir($dh)) { - if ($dir =~ m/^\./) { - next; - } - if (-d "$projectroot/$project/$ref_dir/$dir") { - opendir my $dh2, "$projectroot/$project/$ref_dir/$dir"; - my @subdirs = grep !m/^\./, readdir $dh2; - closedir($dh2); - foreach my $subdir (@subdirs) { - push @refs, "$dir/$subdir" - } - next; - } - push @refs, $dir; - } - closedir($dh); - foreach my $ref_file (@refs) { - my $ref_id = git_read_hash("$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; - } - - push @reflist, \%ref_item; - } - # sort tags by age - @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; - return \@reflist; -} +## ...................................................................... +## functions printing large fragments of HTML sub git_shortlog_body { # uses global variable $project @@ -1265,6 +1233,191 @@ sub git_heads_body { print "\n"; } +## ---------------------------------------------------------------------- +## functions printing large fragments, format as one of arguments + +sub git_diff_print { + my $from = shift; + my $from_name = shift; + my $to = shift; + my $to_name = shift; + my $format = shift || "html"; + + my $from_tmp = "/dev/null"; + my $to_tmp = "/dev/null"; + my $pid = $$; + + # create tmp from-file + if (defined $from) { + $from_tmp = "$git_temp/gitweb_" . $$ . "_from"; + open my $fd2, "> $from_tmp"; + open my $fd, "-|", $GIT, "cat-file", "blob", $from; + my @file = <$fd>; + print $fd2 @file; + close $fd2; + close $fd; + } + + # create tmp to-file + if (defined $to) { + $to_tmp = "$git_temp/gitweb_" . $$ . "_to"; + open my $fd2, "> $to_tmp"; + open my $fd, "-|", $GIT, "cat-file", "blob", $to; + my @file = <$fd>; + print $fd2 @file; + close $fd2; + close $fd; + } + + open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp"; + if ($format eq "plain") { + undef $/; + print <$fd>; + $/ = "\n"; + } else { + while (my $line = <$fd>) { + chomp $line; + my $char = substr($line, 0, 1); + my $diff_class = ""; + if ($char eq '+') { + $diff_class = " add"; + } elsif ($char eq "-") { + $diff_class = " rem"; + } elsif ($char eq "@") { + $diff_class = " chunk_header"; + } elsif ($char eq "\\") { + # skip errors + next; + } + while ((my $pos = index($line, "\t")) != -1) { + if (my $count = (8 - (($pos-1) % 8))) { + my $spaces = ' ' x $count; + $line =~ s/\t/$spaces/; + } + } + print "
" . esc_html($line) . "
\n"; + } + } + close $fd; + + if (defined $from) { + unlink($from_tmp); + } + if (defined $to) { + unlink($to_tmp); + } +} + + +## ====================================================================== +## ====================================================================== +## actions + +# git-logo (cached in browser for one day) +sub git_logo { + binmode STDOUT, ':raw'; + print $cgi->header(-type => 'image/png', -expires => '+1d'); + # cat git-logo.png | hexdump -e '16/1 " %02x" "\n"' | sed 's/ /\\x/g' + print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" . + "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" . + "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" . + "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" . + "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" . + "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" . + "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" . + "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" . + "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" . + "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" . + "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" . + "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" . + "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; +} + +sub git_project_list { + my @list = git_read_projects(); + my @projects; + if (!@list) { + die_error(undef, "No project found."); + } + foreach my $pr (@list) { + my $head = git_read_head($pr->{'path'}); + if (!defined $head) { + next; + } + $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}"; + my %co = git_read_commit($head); + if (!%co) { + next; + } + $pr->{'commit'} = \%co; + if (!defined $pr->{'descr'}) { + my $descr = git_read_description($pr->{'path'}) || ""; + $pr->{'descr'} = chop_str($descr, 25, 5); + } + if (!defined $pr->{'owner'}) { + $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; + } + push @projects, $pr; + } + git_header_html(); + if (-f $home_text) { + print "
\n"; + open (my $fd, $home_text); + print <$fd>; + close $fd; + print "
\n"; + } + print "\n" . + "\n"; + if (!defined($order) || (defined($order) && ($order eq "project"))) { + @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; + print "\n"; + } else { + print "\n"; + } + if (defined($order) && ($order eq "descr")) { + @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; + print "\n"; + } else { + print "\n"; + } + if (defined($order) && ($order eq "owner")) { + @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; + print "\n"; + } else { + print "\n"; + } + if (defined($order) && ($order eq "age")) { + @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; + print "\n"; + } else { + print "\n"; + } + print "\n" . + "\n"; + my $alternate = 0; + foreach my $pr (@projects) { + if ($alternate) { + print "\n"; + } else { + print "\n"; + } + $alternate ^= 1; + print "\n" . + "\n" . + "\n"; + print "\n" . + "\n" . + "\n"; + } + print "
Project" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "Description" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "Owner" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "Last Change" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "
" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "" . esc_html($pr->{'descr'}) . "" . chop_str($pr->{'owner'}, 15) . "{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . + "
\n"; + git_footer_html(); +} + sub git_summary { my $descr = git_read_description($project) || "none"; my $head = git_read_head($project); @@ -1326,20 +1479,6 @@ sub git_summary { git_footer_html(); } -sub git_print_page_path { - my $name = shift; - my $type = shift; - - if (!defined $name) { - print "
/
\n"; - } elsif ($type =~ "blob") { - print "
" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "
\n"; - } else { - print "
" . esc_html($name) . "
\n"; - } -} - sub git_tag { my $head = git_read_head($project); git_header_html(); @@ -1546,84 +1685,6 @@ sub git_heads { git_footer_html(); } -sub git_get_hash_by_path { - my $base = shift; - my $path = shift || return undef; - - my $tree = $base; - - open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path - or die_error(undef, "Open git-ls-tree failed."); - my $line = <$fd>; - close $fd or return undef; - - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; - return $3; -} - -sub mimetype_guess_file { - my $filename = shift; - my $mimemap = shift; - -r $mimemap or return undef; - - my %mimemap; - open(MIME, $mimemap) or return undef; - while () { - my ($mime, $exts) = split(/\t+/); - my @exts = split(/\s+/, $exts); - foreach my $ext (@exts) { - $mimemap{$ext} = $mime; - } - } - close(MIME); - - $filename =~ /\.(.*?)$/; - return $mimemap{$1}; -} - -sub mimetype_guess { - my $filename = shift; - my $mime; - $filename =~ /\./ or return undef; - - if ($mimetypes_file) { - my $file = $mimetypes_file; - #$file =~ m#^/# or $file = "$projectroot/$path/$file"; - $mime = mimetype_guess_file($filename, $file); - } - $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); - return $mime; -} - -sub git_blob_plain_mimetype { - my $fd = shift; - my $filename = shift; - - if ($filename) { - my $mime = mimetype_guess($filename); - $mime and return $mime; - } - - # just in case - return $default_blob_plain_mimetype unless $fd; - - if (-T $fd) { - return 'text/plain' . - ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); - } elsif (! $filename) { - return 'application/octet-stream'; - } elsif ($filename =~ m/\.png$/i) { - return 'image/png'; - } elsif ($filename =~ m/\.gif$/i) { - return 'image/gif'; - } elsif ($filename =~ m/\.jpe?g$/i) { - return 'image/jpeg'; - } else { - return 'application/octet-stream'; - } -} - sub git_blob_plain { if (!defined $hash) { if (defined $file_name) { @@ -1793,98 +1854,6 @@ sub git_tree { git_footer_html(); } -sub git_rss { - # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ - open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) - or die_error(undef, "Open git-rev-list failed."); - my @revlist = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading rev-list failed."); - print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "\n". - "\n"; - print "\n"; - print "$project\n". - "" . esc_html("$my_url?p=$project;a=summary") . "\n". - "$project log\n". - "en\n"; - - for (my $i = 0; $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my %co = git_read_commit($commit); - # we read 150, we always show 30 and the ones more recent than 48 hours - if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { - last; - } - my %cd = date_str($co{'committer_epoch'}); - open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; - my @difftree = map { chomp; $_ } <$fd>; - close $fd or next; - print "\n" . - "" . - sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . - "\n" . - "" . esc_html($co{'author'}) . "\n" . - "$cd{'rfc2822'}\n" . - "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . - "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . - "" . esc_html($co{'title'}) . "\n" . - "" . - "\n"; - } - print "
\n"; - foreach my $line (@difftree) { - if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { - next; - } - my $file = validate_input(unquote($7)); - $file = decode("utf8", $file, Encode::FB_DEFAULT); - print "$file
\n"; - } - print "]]>\n" . - "
\n" . - "
\n"; - } - print "
"; -} - -sub git_opml { - my @list = git_read_projects(); - - print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "\n". - "\n". - "". - " $site_name Git OPML Export\n". - "\n". - "\n". - "\n"; - - foreach my $pr (@list) { - my %proj = %$pr; - my $head = git_read_head($proj{'path'}); - if (!defined $head) { - next; - } - $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; - my %co = git_read_commit($head); - if (!%co) { - next; - } - - my $path = esc_html(chop_str($proj{'path'}, 25, 5)); - my $rss = "$my_url?p=$proj{'path'};a=rss"; - my $html = "$my_url?p=$proj{'path'};a=summary"; - print "\n"; - } - print "\n". - "\n". - "\n"; -} - sub git_log { my $head = git_read_head($project); if (!defined $hash) { @@ -2556,3 +2525,98 @@ sub git_shortlog { git_footer_html(); } + +## ...................................................................... +## feeds (RSS, OPML) + +sub git_rss { + # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ + open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) + or die_error(undef, "Open git-rev-list failed."); + my @revlist = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading rev-list failed."); + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print "\n". + "\n"; + print "\n"; + print "$project\n". + "" . esc_html("$my_url?p=$project;a=summary") . "\n". + "$project log\n". + "en\n"; + + for (my $i = 0; $i <= $#revlist; $i++) { + my $commit = $revlist[$i]; + my %co = git_read_commit($commit); + # we read 150, we always show 30 and the ones more recent than 48 hours + if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { + last; + } + my %cd = date_str($co{'committer_epoch'}); + open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; + my @difftree = map { chomp; $_ } <$fd>; + close $fd or next; + print "\n" . + "" . + sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . + "\n" . + "" . esc_html($co{'author'}) . "\n" . + "$cd{'rfc2822'}\n" . + "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . + "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" . + "" . esc_html($co{'title'}) . "\n" . + "" . + "\n"; + } + print "
\n"; + foreach my $line (@difftree) { + if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { + next; + } + my $file = validate_input(unquote($7)); + $file = decode("utf8", $file, Encode::FB_DEFAULT); + print "$file
\n"; + } + print "]]>\n" . + "
\n" . + "
\n"; + } + print "
"; +} + +sub git_opml { + my @list = git_read_projects(); + + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print "\n". + "\n". + "". + " $site_name Git OPML Export\n". + "\n". + "\n". + "\n"; + + foreach my $pr (@list) { + my %proj = %$pr; + my $head = git_read_head($proj{'path'}); + if (!defined $head) { + next; + } + $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; + my %co = git_read_commit($head); + if (!%co) { + next; + } + + my $path = esc_html(chop_str($proj{'path'}, 25, 5)); + my $rss = "$my_url?p=$proj{'path'};a=rss"; + my $html = "$my_url?p=$proj{'path'};a=summary"; + print "\n"; + } + print "\n". + "\n". + "\n"; +}