'pathinfo' => {
'override' => 0,
'default' => [0]},
+
+ # Make gitweb consider projects in project root subdirectories
+ # to be forks of existing projects. Given project $projname.git,
+ # projects matching $projname/*.git will not be shown in the main
+ # projects list, instead a '+' mark will be added to $projname
+ # there and a 'forks' view will be enabled for the project, listing
+ # all the forks. This feature is supported only if project list
+ # is taken from a directory, not file.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'forks'}{'default'} = [1];
+ # Project specific override is not supported.
+ 'forks' => {
+ 'override' => 0,
+ 'default' => [0]},
);
sub gitweb_check_feature {
"commitdiff" => \&git_commitdiff,
"commitdiff_plain" => \&git_commitdiff_plain,
"commit" => \&git_commit,
+ "forks" => \&git_forks,
"heads" => \&git_heads,
"history" => \&git_history,
"log" => \&git_log,
"rss" => \&git_rss,
+ "atom" => \&git_atom,
"search" => \&git_search,
"search_help" => \&git_search_help,
"shortlog" => \&git_shortlog,
sub href(%) {
my %params = @_;
- my $href = $my_uri;
+ # default is to use -absolute url() i.e. $my_uri
+ my $href = $params{-full} ? $my_url : $my_uri;
# XXX: Warning: If you touch this, check the search form for updating,
# too.
}
# replace invalid utf8 character with SUBSTITUTION sequence
-sub esc_html {
+sub esc_html ($;%) {
my $str = shift;
+ my %opts = @_;
+
$str = to_utf8($str);
$str = escapeHTML($str);
- $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file)
- $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1)
+ if ($opts{'-nbsp'}) {
+ $str =~ s/ / /g;
+ }
+ $str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg;
+ return $str;
+}
+
+# Make control characterss "printable".
+sub quot_cec {
+ my $cntrl = shift;
+ my %es = ( # character escape codes, aka escape sequences
+ "\t" => '\t', # tab (HT)
+ "\n" => '\n', # line feed (LF)
+ "\r" => '\r', # carrige return (CR)
+ "\f" => '\f', # form feed (FF)
+ "\b" => '\b', # backspace (BS)
+ "\a" => '\a', # alarm (bell) (BEL)
+ "\e" => '\e', # escape (ESC)
+ "\013" => '\v', # vertical tab (VT)
+ "\000" => '\0', # nul character (NUL)
+ );
+ my $chr = ( (exists $es{$cntrl})
+ ? $es{$cntrl}
+ : sprintf('\%03o', ord($cntrl)) );
+ return "<span class=\"cntrl\">$chr</span>";
+}
+
+# Alternatively use unicode control pictures codepoints.
+sub quot_upr {
+ my $cntrl = shift;
+ my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
+ return "<span class=\"cntrl\">$chr</span>";
+}
+
+# quote control characters and escape filename to HTML
+sub esc_path {
+ my $str = shift;
+
+ $str = esc_html($str);
+ $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
return $str;
}
# git may return quoted and escaped filenames
sub unquote {
my $str = shift;
+
+ sub unq {
+ my $seq = shift;
+ my %es = ( # character escape codes, aka escape sequences
+ 't' => "\t", # tab (HT, TAB)
+ 'n' => "\n", # newline (NL)
+ 'r' => "\r", # return (CR)
+ 'f' => "\f", # form feed (FF)
+ 'b' => "\b", # backspace (BS)
+ 'a' => "\a", # alarm (bell) (BEL)
+ 'e' => "\e", # escape (ESC)
+ 'v' => "\013", # vertical tab (VT)
+ );
+
+ if ($seq =~ m/^[0-7]{1,3}$/) {
+ # octal char sequence
+ return chr(oct($seq));
+ } elsif (exists $es{$seq}) {
+ # C escape sequence, aka character escape code
+ return $es{$seq}
+ }
+ # quoted ordinary character
+ return $seq;
+ }
+
if ($str =~ m/^"(.*)"$/) {
+ # needs unquoting
$str = $1;
- $str =~ s/\\([0-7]{1,3})/chr(oct($1))/eg;
+ $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;
}
return $str;
}
}
}
+# convert file mode in octal to file type description string
+sub file_type_long {
+ my $mode = shift;
+
+ if ($mode !~ m/^[0-7]+$/) {
+ return $mode;
+ } else {
+ $mode = oct $mode;
+ }
+
+ if (S_ISDIR($mode & S_IFMT)) {
+ return "directory";
+ } elsif (S_ISLNK($mode)) {
+ return "symlink";
+ } elsif (S_ISREG($mode)) {
+ if ($mode & S_IXUSR) {
+ return "executable";
+ } else {
+ 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
+# format line of commit message.
sub format_log_line_html {
my $line = shift;
- $line = esc_html($line);
- $line =~ s/ / /g;
+ $line = esc_html($line, -nbsp=>1);
if ($line =~ m/([0-9a-fA-F]{40})/) {
my $hash_text = $1;
if (git_get_type($hash_text) eq "commit") {
}
}
+# format patch (diff) line (rather not to be used for diff headers)
sub format_diff_line {
my $line = shift;
+ my ($from, $to) = @_;
my $char = substr($line, 0, 1);
my $diff_class = "";
$diff_class = " incomplete";
}
$line = untabify($line);
- return "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
+ if ($from && $to && $line =~ m/^\@{2} /) {
+ my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
+ $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
+
+ $from_lines = 0 unless defined $from_lines;
+ $to_lines = 0 unless defined $to_lines;
+
+ if ($from->{'href'}) {
+ $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
+ -class=>"list"}, $from_text);
+ }
+ if ($to->{'href'}) {
+ $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
+ -class=>"list"}, $to_text);
+ }
+ $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
+ "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+ return "<div class=\"diff$diff_class\">$line</div>\n";
+ }
+ return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
}
## ----------------------------------------------------------------------
}
sub git_get_projects_list {
+ my ($filter) = @_;
my @list;
+ $filter ||= '';
+ $filter =~ s/\.git$//;
+
if (-d $projects_list) {
# search in directory
- my $dir = $projects_list;
+ my $dir = $projects_list . ($filter ? "/$filter" : '');
+ # remove the trailing "/"
+ $dir =~ s!/+$!!;
my $pfxlen = length("$dir");
+ my ($check_forks) = gitweb_check_feature('forks');
+
File::Find::find({
follow_fast => 1, # follow symbolic links
dangling_symlinks => 0, # ignore dangling symlinks, silently
my $subdir = substr($File::Find::name, $pfxlen + 1);
# we check related file in $projectroot
- if (check_export_ok("$projectroot/$subdir")) {
- push @list, { path => $subdir };
+ if ($check_forks and $subdir =~ m#/.#) {
+ $File::Find::prune = 1;
+ } elsif (check_export_ok("$projectroot/$filter/$subdir")) {
+ push @list, { path => ($filter ? "$filter/" : '') . $subdir };
$File::Find::prune = 1;
}
},
if (!defined $path) {
next;
}
+ if ($filter ne '') {
+ # looking for forks;
+ my $pfx = substr($path, 0, length($filter));
+ if ($pfx ne $filter) {
+ next;
+ }
+ my $sfx = substr($path, length($filter));
+ if ($sfx !~ /^\/.*\.git$/) {
+ next;
+ }
+ }
if (check_export_ok("$projectroot/$path")) {
my $pr = {
path => $path,
$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{'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;
+ $date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
+ 1900+$year, $mon, $mday, $hour ,$min, $sec;
$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
my $local = $epoch + ((int $1 + ($2/60)) * 3600);
$date{'hour_local'} = $hour;
$date{'minute_local'} = $min;
$date{'tz_local'} = $tz;
- $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
- 1900+$year, $mon+1, $mday,
- $hour, $min, $sec, $tz);
+ $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
+ 1900+$year, $mon+1, $mday,
+ $hour, $min, $sec, $tz);
return %date;
}
if (defined $action) {
$title .= "/$action";
if (defined $file_name) {
- $title .= " - " . esc_html($file_name);
+ $title .= " - " . esc_path($file_name);
if ($action eq "tree" && $file_name !~ m|/$|) {
$title .= "/";
}
}
}
if (defined $project) {
- printf('<link rel="alternate" title="%s log" '.
- 'href="%s" type="application/rss+xml"/>'."\n",
+ printf('<link rel="alternate" title="%s log RSS feed" '.
+ 'href="%s" type="application/rss+xml" />'."\n",
esc_param($project), href(action=>"rss"));
+ printf('<link rel="alternate" title="%s log Atom feed" '.
+ 'href="%s" type="application/atom+xml" />'."\n",
+ esc_param($project), href(action=>"atom"));
} else {
printf('<link rel="alternate" title="%s projects list" '.
'href="%s" type="text/plain; charset=utf-8"/>'."\n",
$site_name, href(project=>undef, action=>"project_index"));
- printf('<link rel="alternate" title="%s projects logs" '.
+ printf('<link rel="alternate" title="%s projects feeds" '.
'href="%s" type="text/x-opml"/>'."\n",
$site_name, href(project=>undef, action=>"opml"));
}
print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
}
print $cgi->a({-href => href(action=>"rss"),
- -class => "rss_logo"}, "RSS") . "\n";
+ -class => "rss_logo"}, "RSS") . " ";
+ print $cgi->a({-href => href(action=>"atom"),
+ -class => "rss_logo"}, "Atom") . "\n";
} else {
print $cgi->a({-href => href(project=>undef, action=>"opml"),
-class => "rss_logo"}, "OPML") . " ";
$fullname .= ($fullname ? '/' : '') . $dir;
print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
hash_base=>$hb),
- -title => $fullname}, esc_html($dir));
+ -title => esc_html($fullname)}, esc_path($dir));
print " / ";
}
if (defined $type && $type eq 'blob') {
print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
hash_base=>$hb),
- -title => $name}, esc_html($basename));
+ -title => esc_html($name)}, esc_path($basename));
} elsif (defined $type && $type eq 'tree') {
print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
hash_base=>$hb),
- -title => $name}, esc_html($basename));
+ -title => esc_html($name)}, esc_path($basename));
print " / ";
} else {
- print esc_html($basename);
+ print esc_path($basename);
}
}
print "<br/></div>\n";
print "<td class=\"list\">" .
$cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
file_name=>"$basedir$t->{'name'}", %base_key),
- -class => "list"}, esc_html($t->{'name'})) . "</td>\n";
+ -class => "list"}, esc_path($t->{'name'})) . "</td>\n";
print "<td class=\"link\">";
print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
file_name=>"$basedir$t->{'name'}", %base_key)},
print "<td class=\"list\">";
print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
file_name=>"$basedir$t->{'name'}", %base_key)},
- esc_html($t->{'name'}));
+ esc_path($t->{'name'}));
print "</td>\n";
print "<td class=\"link\">";
print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
sub git_difftree_body {
my ($difftree, $hash, $parent) = @_;
-
+ my ($have_blame) = gitweb_check_feature('blame');
print "<div class=\"list_head\">\n";
if ($#{$difftree} > 10) {
print(($#{$difftree} + 1) . " files changed:\n");
print "<td>";
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'}),
- -class => "list"}, esc_html($diff{'file'}));
+ -class => "list"}, esc_path($diff{'file'}));
print "</td>\n";
print "<td>$mode_chng</td>\n";
print "<td class=\"link\">";
# link to patch
$patchno++;
print $cgi->a({-href => "#patch$patchno"}, "patch");
+ print " | ";
}
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$hash, file_name=>$diff{'file'})},
+ "blob") . " | ";
print "</td>\n";
} elsif ($diff{'status'} eq "D") { # deleted
print "<td>";
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
hash_base=>$parent, file_name=>$diff{'file'}),
- -class => "list"}, esc_html($diff{'file'}));
+ -class => "list"}, esc_path($diff{'file'}));
print "</td>\n";
print "<td>$mode_chng</td>\n";
print "<td class=\"link\">";
}
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
hash_base=>$parent, file_name=>$diff{'file'})},
- "blob") . " | ";
- print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
- file_name=>$diff{'file'})},
- "blame") . " | ";
+ "blob") . " | ";
+ if ($have_blame) {
+ print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
+ file_name=>$diff{'file'})},
+ "blame") . " | ";
+ }
print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
file_name=>$diff{'file'})},
"history");
print "<td>";
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'}),
- -class => "list"}, esc_html($diff{'file'}));
+ -class => "list"}, esc_path($diff{'file'}));
print "</td>\n";
print "<td>$mode_chnge</td>\n";
print "<td class=\"link\">";
- if ($diff{'to_id'} ne $diff{'from_id'}) { # modified
- if ($action eq 'commitdiff') {
- # link to patch
- $patchno++;
- print $cgi->a({-href => "#patch$patchno"}, "patch");
- } else {
- print $cgi->a({-href => href(action=>"blobdiff",
- hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
- hash_base=>$hash, hash_parent_base=>$parent,
- file_name=>$diff{'file'})},
- "diff");
- }
- print " | ";
+ if ($action eq 'commitdiff') {
+ # link to patch
+ $patchno++;
+ print $cgi->a({-href => "#patch$patchno"}, "patch") .
+ " | ";
+ } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+ # "commit" view and modified file (not onlu mode changed)
+ print $cgi->a({-href => href(action=>"blobdiff",
+ hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+ hash_base=>$hash, hash_parent_base=>$parent,
+ file_name=>$diff{'file'})},
+ "diff") .
+ " | ";
}
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
- hash_base=>$hash, file_name=>$diff{'file'})},
- "blob") . " | ";
- print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
- file_name=>$diff{'file'})},
- "blame") . " | ";
+ hash_base=>$hash, file_name=>$diff{'file'})},
+ "blob") . " | ";
+ if ($have_blame) {
+ print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
+ file_name=>$diff{'file'})},
+ "blame") . " | ";
+ }
print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
file_name=>$diff{'file'})},
"history");
print "<td>" .
$cgi->a({-href => href(action=>"blob", hash_base=>$hash,
hash=>$diff{'to_id'}, file_name=>$diff{'to_file'}),
- -class => "list"}, esc_html($diff{'to_file'})) . "</td>\n" .
+ -class => "list"}, esc_path($diff{'to_file'})) . "</td>\n" .
"<td><span class=\"file_status $nstatus\">[$nstatus from " .
$cgi->a({-href => href(action=>"blob", hash_base=>$parent,
hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}),
- -class => "list"}, esc_html($diff{'from_file'})) .
+ -class => "list"}, esc_path($diff{'from_file'})) .
" with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
"<td class=\"link\">";
- if ($diff{'to_id'} ne $diff{'from_id'}) {
- if ($action eq 'commitdiff') {
- # link to patch
- $patchno++;
- print $cgi->a({-href => "#patch$patchno"}, "patch");
- } else {
- print $cgi->a({-href => href(action=>"blobdiff",
- hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
- hash_base=>$hash, hash_parent_base=>$parent,
- file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
- "diff");
- }
- print " | ";
+ if ($action eq 'commitdiff') {
+ # link to patch
+ $patchno++;
+ print $cgi->a({-href => "#patch$patchno"}, "patch") .
+ " | ";
+ } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+ # "commit" view and modified file (not only pure rename or copy)
+ print $cgi->a({-href => href(action=>"blobdiff",
+ hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+ hash_base=>$hash, hash_parent_base=>$parent,
+ file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
+ "diff") .
+ " | ";
}
- print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
- hash_base=>$parent, file_name=>$diff{'from_file'})},
- "blob") . " | ";
- print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
- file_name=>$diff{'from_file'})},
- "blame") . " | ";
- print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
- file_name=>$diff{'from_file'})},
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$parent, file_name=>$diff{'to_file'})},
+ "blob") . " | ";
+ if ($have_blame) {
+ print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
+ file_name=>$diff{'to_file'})},
+ "blame") . " | ";
+ }
+ print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
+ file_name=>$diff{'to_file'})},
"history");
print "</td>\n";
my ($fd, $difftree, $hash, $hash_parent) = @_;
my $patch_idx = 0;
- my $in_header = 0;
- my $patch_found = 0;
+ my $patch_line;
my $diffinfo;
+ my (%from, %to);
+ my ($from_id, $to_id);
print "<div class=\"patchset\">\n";
- LINE:
- while (my $patch_line = <$fd>) {
+ # skip to first patch
+ while ($patch_line = <$fd>) {
chomp $patch_line;
- if ($patch_line =~ m/^diff /) { # "git diff" header
- # beginning of patch (in patchset)
- if ($patch_found) {
- # close previous patch
- print "</div>\n"; # class="patch"
- } else {
- # first patch in patchset
- $patch_found = 1;
+ last if ($patch_line =~ m/^diff /);
+ }
+
+ PATCH:
+ while ($patch_line) {
+ my @diff_header;
+
+ # git diff header
+ #assert($patch_line =~ m/^diff /) if DEBUG;
+ #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
+ push @diff_header, $patch_line;
+
+ # extended diff header
+ EXTENDED_HEADER:
+ while ($patch_line = <$fd>) {
+ chomp $patch_line;
+
+ last EXTENDED_HEADER if ($patch_line =~ m/^--- /);
+
+ if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
+ $from_id = $1;
+ $to_id = $2;
}
- print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
+ push @diff_header, $patch_line;
+ }
+ #last PATCH unless $patch_line;
+ my $last_patch_line = $patch_line;
+
+ # check if current patch belong to current raw line
+ # and parse raw git-diff line if needed
+ if (defined $diffinfo &&
+ $diffinfo->{'from_id'} eq $from_id &&
+ $diffinfo->{'to_id'} eq $to_id) {
+ # this is split patch
+ print "<div class=\"patch cont\">\n";
+ } else {
+ # advance raw git-diff output if needed
+ $patch_idx++ if defined $diffinfo;
+
+ # read and prepare patch information
if (ref($difftree->[$patch_idx]) eq "HASH") {
+ # pre-parsed (or generated by hand)
$diffinfo = $difftree->[$patch_idx];
} else {
$diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
}
- $patch_idx++;
-
- # for now, no extended header, hence we skip empty patches
- # companion to next LINE if $in_header;
- if ($diffinfo->{'from_id'} eq $diffinfo->{'to_id'}) { # no change
- $in_header = 1;
- next LINE;
+ $from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
+ $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
+ if ($diffinfo->{'status'} ne "A") { # not new (added) file
+ $from{'href'} = href(action=>"blob", hash_base=>$hash_parent,
+ hash=>$diffinfo->{'from_id'},
+ file_name=>$from{'file'});
}
-
- if ($diffinfo->{'status'} eq "A") { # added
- print "<div class=\"diff_info\">" . file_type($diffinfo->{'to_mode'}) . ":" .
- $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
- hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})},
- $diffinfo->{'to_id'}) . " (new)" .
- "</div>\n"; # class="diff_info"
-
- } elsif ($diffinfo->{'status'} eq "D") { # deleted
- print "<div class=\"diff_info\">" . file_type($diffinfo->{'from_mode'}) . ":" .
- $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
- hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})},
- $diffinfo->{'from_id'}) . " (deleted)" .
- "</div>\n"; # class="diff_info"
-
- } elsif ($diffinfo->{'status'} eq "R" || # renamed
- $diffinfo->{'status'} eq "C" || # copied
- $diffinfo->{'status'} eq "2") { # with two filenames (from git_blobdiff)
- print "<div class=\"diff_info\">" .
- file_type($diffinfo->{'from_mode'}) . ":" .
- $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
- hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'from_file'})},
- $diffinfo->{'from_id'}) .
- " -> " .
- file_type($diffinfo->{'to_mode'}) . ":" .
- $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
- hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'to_file'})},
- $diffinfo->{'to_id'});
- print "</div>\n"; # class="diff_info"
-
- } else { # modified, mode changed, ...
- print "<div class=\"diff_info\">" .
- file_type($diffinfo->{'from_mode'}) . ":" .
- $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
- hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})},
- $diffinfo->{'from_id'}) .
- " -> " .
- file_type($diffinfo->{'to_mode'}) . ":" .
- $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
- hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})},
- $diffinfo->{'to_id'});
- print "</div>\n"; # class="diff_info"
+ if ($diffinfo->{'status'} ne "D") { # not deleted file
+ $to{'href'} = href(action=>"blob", hash_base=>$hash,
+ hash=>$diffinfo->{'to_id'},
+ file_name=>$to{'file'});
}
+ # this is first patch for raw difftree line with $patch_idx index
+ # we index @$difftree array from 0, but number patches from 1
+ print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
+ }
- #print "<div class=\"diff extended_header\">\n";
- $in_header = 1;
- next LINE;
- } # start of patch in patchset
-
+ # print "git diff" header
+ $patch_line = shift @diff_header;
+ $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
+ if ($from{'href'}) {
+ $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
+ 'a/' . esc_path($from{'file'}));
+ } else { # file was added
+ $patch_line .= 'a/' . esc_path($from{'file'});
+ }
+ $patch_line .= ' ';
+ if ($to{'href'}) {
+ $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
+ 'b/' . esc_path($to{'file'}));
+ } else { # file was deleted
+ $patch_line .= 'b/' . esc_path($to{'file'});
+ }
+ print "<div class=\"diff header\">$patch_line</div>\n";
+
+ # print extended diff header
+ print "<div class=\"diff extended_header\">\n" if (@diff_header > 0);
+ EXTENDED_HEADER:
+ foreach $patch_line (@diff_header) {
+ # match <path>
+ if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) {
+ $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"},
+ esc_path($from{'file'}));
+ }
+ if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) {
+ $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"},
+ esc_path($to{'file'}));
+ }
+ # match <mode>
+ if ($patch_line =~ m/\s(\d{6})$/) {
+ $patch_line .= '<span class="info"> (' .
+ file_type_long($1) .
+ ')</span>';
+ }
+ # match <hash>
+ if ($patch_line =~ m/^index/) {
+ my ($from_link, $to_link);
+ if ($from{'href'}) {
+ $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
+ substr($diffinfo->{'from_id'},0,7));
+ } else {
+ $from_link = '0' x 7;
+ }
+ if ($to{'href'}) {
+ $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"},
+ substr($diffinfo->{'to_id'},0,7));
+ } else {
+ $to_link = '0' x 7;
+ }
+ #affirm {
+ # my ($from_hash, $to_hash) =
+ # ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/);
+ # my ($from_id, $to_id) =
+ # ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
+ # ($from_hash eq $from_id) && ($to_hash eq $to_id);
+ #} if DEBUG;
+ my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
+ $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
+ }
+ print $patch_line . "<br/>\n";
+ }
+ print "</div>\n" if (@diff_header > 0); # class="diff extended_header"
+
+ # from-file/to-file diff header
+ $patch_line = $last_patch_line;
+ #assert($patch_line =~ m/^---/) if DEBUG;
+ if ($from{'href'}) {
+ $patch_line = '--- a/' .
+ $cgi->a({-href=>$from{'href'}, -class=>"path"},
+ esc_path($from{'file'}));
+ }
+ print "<div class=\"diff from_file\">$patch_line</div>\n";
- if ($in_header && $patch_line =~ m/^---/) {
- #print "</div>\n"; # class="diff extended_header"
- $in_header = 0;
+ $patch_line = <$fd>;
+ #last PATCH unless $patch_line;
+ chomp $patch_line;
- my $file = $diffinfo->{'from_file'};
- $file ||= $diffinfo->{'file'};
- $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
- hash=>$diffinfo->{'from_id'}, file_name=>$file),
- -class => "list"}, esc_html($file));
- $patch_line =~ s|a/.*$|a/$file|g;
- print "<div class=\"diff from_file\">$patch_line</div>\n";
+ #assert($patch_line =~ m/^+++/) if DEBUG;
+ if ($to{'href'}) {
+ $patch_line = '+++ b/' .
+ $cgi->a({-href=>$to{'href'}, -class=>"path"},
+ esc_path($to{'file'}));
+ }
+ print "<div class=\"diff to_file\">$patch_line</div>\n";
- $patch_line = <$fd>;
+ # the patch itself
+ LINE:
+ while ($patch_line = <$fd>) {
chomp $patch_line;
- #$patch_line =~ m/^+++/;
- $file = $diffinfo->{'to_file'};
- $file ||= $diffinfo->{'file'};
- $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
- hash=>$diffinfo->{'to_id'}, file_name=>$file),
- -class => "list"}, esc_html($file));
- $patch_line =~ s|b/.*|b/$file|g;
- print "<div class=\"diff to_file\">$patch_line</div>\n";
+ next PATCH if ($patch_line =~ m/^diff /);
- next LINE;
+ print format_diff_line($patch_line, \%from, \%to);
}
- next LINE if $in_header;
- print format_diff_line($patch_line);
+ } continue {
+ print "</div>\n"; # class="patch"
}
- print "</div>\n" if $patch_found; # class="patch"
print "</div>\n"; # class="patchset"
}
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+sub git_project_list_body {
+ my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
+
+ my ($check_forks) = gitweb_check_feature('forks');
+
+ my @projects;
+ foreach my $pr (@$projlist) {
+ my (@aa) = git_get_last_activity($pr->{'path'});
+ unless (@aa) {
+ next;
+ }
+ ($pr->{'age'}, $pr->{'age_string'}) = @aa;
+ if (!defined $pr->{'descr'}) {
+ my $descr = git_get_project_description($pr->{'path'}) || "";
+ $pr->{'descr'} = chop_str($descr, 25, 5);
+ }
+ if (!defined $pr->{'owner'}) {
+ $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
+ }
+ if ($check_forks) {
+ my $pname = $pr->{'path'};
+ if (($pname =~ s/\.git$//) &&
+ ($pname !~ /\/$/) &&
+ (-d "$projectroot/$pname")) {
+ $pr->{'forks'} = "-d $projectroot/$pname";
+ }
+ else {
+ $pr->{'forks'} = 0;
+ }
+ }
+ push @projects, $pr;
+ }
+
+ $order ||= "project";
+ $from = 0 unless defined $from;
+ $to = $#projects if (!defined $to || $#projects < $to);
+
+ print "<table class=\"project_list\">\n";
+ unless ($no_header) {
+ print "<tr>\n";
+ if ($check_forks) {
+ print "<th></th>\n";
+ }
+ if ($order eq "project") {
+ @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
+ print "<th>Project</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'project'),
+ -class => "header"}, "Project") .
+ "</th>\n";
+ }
+ if ($order eq "descr") {
+ @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
+ print "<th>Description</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'descr'),
+ -class => "header"}, "Description") .
+ "</th>\n";
+ }
+ if ($order eq "owner") {
+ @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
+ print "<th>Owner</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'owner'),
+ -class => "header"}, "Owner") .
+ "</th>\n";
+ }
+ if ($order eq "age") {
+ @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
+ print "<th>Last Change</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'age'),
+ -class => "header"}, "Last Change") .
+ "</th>\n";
+ }
+ print "<th></th>\n" .
+ "</tr>\n";
+ }
+ my $alternate = 1;
+ for (my $i = $from; $i <= $to; $i++) {
+ my $pr = $projects[$i];
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ if ($check_forks) {
+ print "<td>";
+ if ($pr->{'forks'}) {
+ print "<!-- $pr->{'forks'} -->\n";
+ print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
+ }
+ print "</td>\n";
+ }
+ print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+ -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
+ "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
+ "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
+ print "<td class=\"". age_class($pr->{'age'}) . "\">" .
+ $pr->{'age_string'} . "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
+ $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
+ $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
+ $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
+ ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
+ "</td>\n" .
+ "</tr>\n";
+ }
+ if (defined $extra) {
+ print "<tr>\n";
+ if ($check_forks) {
+ print "<td></td>\n";
+ }
+ print "<td colspan=\"5\">$extra</td>\n" .
+ "</tr>\n";
+ }
+ print "</table>\n";
+}
+
sub git_shortlog_body {
# uses global variable $project
my ($revlist, $from, $to, $refs, $extra) = @_;
}
my @list = git_get_projects_list();
- my @projects;
if (!@list) {
die_error(undef, "No projects found");
}
- foreach my $pr (@list) {
- my (@aa) = git_get_last_activity($pr->{'path'});
- unless (@aa) {
- next;
- }
- ($pr->{'age'}, $pr->{'age_string'}) = @aa;
- if (!defined $pr->{'descr'}) {
- my $descr = git_get_project_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) {
close $fd;
print "</div>\n";
}
- print "<table class=\"project_list\">\n" .
- "<tr>\n";
- $order ||= "project";
- if ($order eq "project") {
- @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
- print "<th>Project</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => href(project=>undef, order=>'project'),
- -class => "header"}, "Project") .
- "</th>\n";
- }
- if ($order eq "descr") {
- @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
- print "<th>Description</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => href(project=>undef, order=>'descr'),
- -class => "header"}, "Description") .
- "</th>\n";
- }
- if ($order eq "owner") {
- @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
- print "<th>Owner</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => href(project=>undef, order=>'owner'),
- -class => "header"}, "Owner") .
- "</th>\n";
- }
- if ($order eq "age") {
- @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
- print "<th>Last Change</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => href(project=>undef, order=>'age'),
- -class => "header"}, "Last Change") .
- "</th>\n";
+ git_project_list_body(\@list, $order);
+ git_footer_html();
+}
+
+sub git_forks {
+ my $order = $cgi->param('o');
+ if (defined $order && $order !~ m/project|descr|owner|age/) {
+ die_error(undef, "Unknown order parameter");
}
- print "<th></th>\n" .
- "</tr>\n";
- my $alternate = 1;
- foreach my $pr (@projects) {
- if ($alternate) {
- print "<tr class=\"dark\">\n";
- } else {
- print "<tr class=\"light\">\n";
- }
- $alternate ^= 1;
- print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
- -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
- "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
- "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
- print "<td class=\"". age_class($pr->{'age'}) . "\">" .
- $pr->{'age_string'} . "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
- $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
- $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
- $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
- "</td>\n" .
- "</tr>\n";
+
+ my @list = git_get_projects_list($project);
+ if (!@list) {
+ die_error(undef, "No forks found");
}
- print "</table>\n";
+
+ git_header_html();
+ git_print_page_nav('','');
+ git_print_header_div('summary', "$project forks");
+ git_project_list_body(\@list, $order);
git_footer_html();
}
sub git_project_index {
- my @projects = git_get_projects_list();
+ my @projects = git_get_projects_list($project);
print $cgi->header(
-type => 'text/plain',
my $refs = git_get_references();
my @taglist = git_get_tags_list(15);
my @headlist = git_get_heads_list(15);
+ my @forklist;
+ my ($check_forks) = gitweb_check_feature('forks');
+
+ if ($check_forks) {
+ @forklist = git_get_projects_list($project);
+ }
git_header_html();
git_print_page_nav('summary','', $head);
$cgi->a({-href => href(action=>"heads")}, "..."));
}
+ if (@forklist) {
+ git_print_header_div('forks');
+ git_project_list_body(\@forklist, undef, 0, 15,
+ $cgi->a({-href => href(action=>"forks")}, "..."),
+ 'noheader');
+ }
+
git_footer_html();
}
print "<div class=\"page_body\">";
my $comment = $tag{'comment'};
foreach my $line (@$comment) {
+ chomp($line);
print esc_html($line) . "<br/>\n";
}
print "</div>\n";
}
}
my $data = $_;
+ chomp($data);
my $rev = substr($full_rev, 0, 8);
my $author = $meta->{'author'};
my %date = parse_date($meta->{'author-time'},
$nr++;
$line = untabify($line);
printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
- $nr, $nr, $nr, esc_html($line);
+ $nr, $nr, $nr, esc_html($line, -nbsp=>1);
}
close $fd
or print "Reading blob failed.\n";
}
sub git_commit {
+ $hash ||= $hash_base || "HEAD";
my %co = parse_commit($hash);
if (!%co) {
die_error(undef, "Unknown commit object");
} else {
while (my $line = <$fd>) {
- $line =~ s!a/($hash|$hash_parent)!'a/'.esc_html($diffinfo{'from_file'})!eg;
- $line =~ s!b/($hash|$hash_parent)!'b/'.esc_html($diffinfo{'to_file'})!eg;
+ $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg;
+ $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg;
print $line;
sub git_commitdiff {
my $format = shift || 'html';
+ $hash ||= $hash_base || "HEAD";
my %co = parse_commit($hash);
if (!%co) {
die_error(undef, "Unknown commit object");
$hash_parent, $hash, "--"
or die_error(undef, "Open git-diff-tree failed");
- while (chomp(my $line = <$fd>)) {
+ while (my $line = <$fd>) {
+ chomp $line;
# empty line ends raw part of diff-tree output
last unless $line;
push @difftree, $line;
print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
hash=>$set{'id'}, file_name=>$set{'file'}),
-class => "list"},
- "<span class=\"match\">" . esc_html($set{'file'}) . "</span>") .
+ "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
"<br/>\n";
}
print "</td>\n" .
}
## ......................................................................
-## feeds (RSS, OPML)
+## feeds (RSS, Atom; OPML)
-sub git_rss {
- # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
+sub git_feed {
+ my $format = shift || 'atom';
+ my ($have_blame) = gitweb_check_feature('blame');
+
+ # Atom: http://www.atomenabled.org/developers/syndication/
+ # RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
+ if ($format ne 'rss' && $format ne 'atom') {
+ die_error(undef, "Unknown web feed format");
+ }
+
+ # log/feed of current (HEAD) branch, log of given branch, history of file/directory
+ my $head = $hash || 'HEAD';
open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
- git_get_head_hash($project), "--"
+ $head, "--", (defined $file_name ? $file_name : ())
or die_error(undef, "Open git-rev-list failed");
my @revlist = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading git-rev-list failed");
- print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
- print <<XML;
-<?xml version="1.0" encoding="utf-8"?>
+
+ my %latest_commit;
+ my %latest_date;
+ my $content_type = "application/$format+xml";
+ if (defined $cgi->http('HTTP_ACCEPT') &&
+ $cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
+ # browser (feed reader) prefers text/xml
+ $content_type = 'text/xml';
+ }
+ if (defined($revlist[0])) {
+ %latest_commit = parse_commit($revlist[0]);
+ %latest_date = parse_date($latest_commit{'committer_epoch'});
+ print $cgi->header(
+ -type => $content_type,
+ -charset => 'utf-8',
+ -last_modified => $latest_date{'rfc2822'});
+ } else {
+ print $cgi->header(
+ -type => $content_type,
+ -charset => 'utf-8');
+ }
+
+ # Optimization: skip generating the body if client asks only
+ # for Last-Modified date.
+ return if ($cgi->request_method() eq 'HEAD');
+
+ # header variables
+ my $title = "$site_name - $project/$action";
+ my $feed_type = 'log';
+ if (defined $hash) {
+ $title .= " - '$hash'";
+ $feed_type = 'branch log';
+ if (defined $file_name) {
+ $title .= " :: $file_name";
+ $feed_type = 'history';
+ }
+ } elsif (defined $file_name) {
+ $title .= " - $file_name";
+ $feed_type = 'history';
+ }
+ $title .= " $feed_type";
+ my $descr = git_get_project_description($project);
+ if (defined $descr) {
+ $descr = esc_html($descr);
+ } else {
+ $descr = "$project " .
+ ($format eq 'rss' ? 'RSS' : 'Atom') .
+ " feed";
+ }
+ my $owner = git_get_project_owner($project);
+ $owner = esc_html($owner);
+
+ #header
+ my $alt_url;
+ if (defined $file_name) {
+ $alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
+ } elsif (defined $hash) {
+ $alt_url = href(-full=>1, action=>"log", hash=>$hash);
+ } else {
+ $alt_url = href(-full=>1, action=>"summary");
+ }
+ print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
+ if ($format eq 'rss') {
+ print <<XML;
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
-<title>$project $my_uri $my_url</title>
-<link>${\esc_html("$my_url?p=$project;a=summary")}</link>
-<description>$project log</description>
-<language>en</language>
XML
+ print "<title>$title</title>\n" .
+ "<link>$alt_url</link>\n" .
+ "<description>$descr</description>\n" .
+ "<language>en</language>\n";
+ } elsif ($format eq 'atom') {
+ print <<XML;
+<feed xmlns="http://www.w3.org/2005/Atom">
+XML
+ print "<title>$title</title>\n" .
+ "<subtitle>$descr</subtitle>\n" .
+ '<link rel="alternate" type="text/html" href="' .
+ $alt_url . '" />' . "\n" .
+ '<link rel="self" type="' . $content_type . '" href="' .
+ $cgi->self_url() . '" />' . "\n" .
+ "<id>" . href(-full=>1) . "</id>\n" .
+ # use project owner for feed author
+ "<author><name>$owner</name></author>\n";
+ if (defined $favicon) {
+ print "<icon>" . esc_url($favicon) . "</icon>\n";
+ }
+ if (defined $logo_url) {
+ # not twice as wide as tall: 72 x 27 pixels
+ print "<logo>" . esc_url($logo_url) . "</logo>\n";
+ }
+ if (! %latest_date) {
+ # dummy date to keep the feed valid until commits trickle in:
+ print "<updated>1970-01-01T00:00:00Z</updated>\n";
+ } else {
+ print "<updated>$latest_date{'iso-8601'}</updated>\n";
+ }
+ }
+ # contents
for (my $i = 0; $i <= $#revlist; $i++) {
my $commit = $revlist[$i];
my %co = parse_commit($commit);
last;
}
my %cd = parse_date($co{'committer_epoch'});
+
+ # get list of changed files
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
- $co{'parent'}, $co{'id'}, "--"
+ $co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
or next;
my @difftree = map { chomp; $_ } <$fd>;
close $fd
or next;
- print "<item>\n" .
- "<title>" .
- sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
- "</title>\n" .
- "<author>" . esc_html($co{'author'}) . "</author>\n" .
- "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
- "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" .
- "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" .
- "<description>" . esc_html($co{'title'}) . "</description>\n" .
- "<content:encoded>" .
- "<![CDATA[\n";
+
+ # print element (entry, item)
+ my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
+ if ($format eq 'rss') {
+ print "<item>\n" .
+ "<title>" . esc_html($co{'title'}) . "</title>\n" .
+ "<author>" . esc_html($co{'author'}) . "</author>\n" .
+ "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
+ "<guid isPermaLink=\"true\">$co_url</guid>\n" .
+ "<link>$co_url</link>\n" .
+ "<description>" . esc_html($co{'title'}) . "</description>\n" .
+ "<content:encoded>" .
+ "<![CDATA[\n";
+ } elsif ($format eq 'atom') {
+ print "<entry>\n" .
+ "<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
+ "<updated>$cd{'iso-8601'}</updated>\n" .
+ "<author><name>" . esc_html($co{'author_name'}) . "</name></author>\n" .
+ # use committer for contributor
+ "<contributor><name>" . esc_html($co{'committer_name'}) . "</name></contributor>\n" .
+ "<published>$cd{'iso-8601'}</published>\n" .
+ "<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
+ "<id>$co_url</id>\n" .
+ "<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
+ "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
+ }
my $comment = $co{'comment'};
+ print "<pre>\n";
foreach my $line (@$comment) {
- $line = to_utf8($line);
- print "$line<br/>\n";
+ $line = esc_html($line);
+ print "$line\n";
}
- print "<br/>\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;
+ print "</pre><ul>\n";
+ foreach my $difftree_line (@difftree) {
+ my %difftree = parse_difftree_raw_line($difftree_line);
+ next if !$difftree{'from_id'};
+
+ my $file = $difftree{'file'} || $difftree{'to_file'};
+
+ print "<li>" .
+ "[" .
+ $cgi->a({-href => href(-full=>1, action=>"blobdiff",
+ hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
+ hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
+ file_name=>$file, file_parent=>$difftree{'from_file'}),
+ -title => "diff"}, 'D');
+ if ($have_blame) {
+ print $cgi->a({-href => href(-full=>1, action=>"blame",
+ file_name=>$file, hash_base=>$commit),
+ -title => "blame"}, 'B');
}
- my $file = esc_html(unquote($7));
- $file = to_utf8($file);
- print "$file<br/>\n";
+ # if this is not a feed of a file history
+ if (!defined $file_name || $file_name ne $file) {
+ print $cgi->a({-href => href(-full=>1, action=>"history",
+ file_name=>$file, hash=>$commit),
+ -title => "history"}, 'H');
+ }
+ $file = esc_path($file);
+ print "] ".
+ "$file</li>\n";
+ }
+ if ($format eq 'rss') {
+ print "</ul>]]>\n" .
+ "</content:encoded>\n" .
+ "</item>\n";
+ } elsif ($format eq 'atom') {
+ print "</ul>\n</div>\n" .
+ "</content>\n" .
+ "</entry>\n";
}
- print "]]>\n" .
- "</content:encoded>\n" .
- "</item>\n";
}
- print "</channel></rss>";
+
+ # end of feed
+ if ($format eq 'rss') {
+ print "</channel>\n</rss>\n";
+ } elsif ($format eq 'atom') {
+ print "</feed>\n";
+ }
+}
+
+sub git_rss {
+ git_feed('rss');
+}
+
+sub git_atom {
+ git_feed('atom');
}
sub git_opml {