\n"; # class="diff nodifferences"
+
+ return $result;
+}
+
+# format patch (diff) line (not to be used for diff headers)
sub format_diff_line {
my $line = shift;
my ($from, $to) = @_;
@@ -996,6 +1327,43 @@ sub format_diff_line {
return "
" . esc_html($line, -nbsp=>1) . "
\n";
}
+# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
+# linked. Pass the hash of the tree/commit to snapshot.
+sub format_snapshot_links {
+ my ($hash) = @_;
+ my @snapshot_fmts = gitweb_check_feature('snapshot');
+ @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
+ my $num_fmts = @snapshot_fmts;
+ if ($num_fmts > 1) {
+ # A parenthesized list of links bearing format names.
+ # e.g. "snapshot (_tar.gz_ _zip_)"
+ return "snapshot (" . join(' ', map
+ $cgi->a({
+ -href => href(
+ action=>"snapshot",
+ hash=>$hash,
+ snapshot_format=>$_
+ )
+ }, $known_snapshot_formats{$_}{'display'})
+ , @snapshot_fmts) . ")";
+ } elsif ($num_fmts == 1) {
+ # A single "snapshot" link whose tooltip bears the format name.
+ # i.e. "_snapshot_"
+ my ($fmt) = @snapshot_fmts;
+ return
+ $cgi->a({
+ -href => href(
+ action=>"snapshot",
+ hash=>$hash,
+ snapshot_format=>$fmt
+ ),
+ -title => "in format: $known_snapshot_formats{$fmt}{'display'}"
+ }, "snapshot");
+ } else { # $num_fmts == 0
+ return undef;
+ }
+}
+
## ----------------------------------------------------------------------
## git utility subroutines, invoking git commands
@@ -1115,7 +1483,9 @@ sub git_get_project_description {
open my $fd, "$projectroot/$path/description" or return undef;
my $descr = <$fd>;
close $fd;
- chomp $descr;
+ if (defined $descr) {
+ chomp $descr;
+ }
return $descr;
}
@@ -1144,15 +1514,22 @@ sub git_get_projects_list {
# remove the trailing "/"
$dir =~ s!/+$!!;
my $pfxlen = length("$dir");
+ my $pfxdepth = ($dir =~ tr!/!!);
File::Find::find({
follow_fast => 1, # follow symbolic links
+ follow_skip => 2, # ignore duplicates
dangling_symlinks => 0, # ignore dangling symlinks, silently
wanted => sub {
# skip project-list toplevel, if we get it.
return if (m!^[/.]$!);
# only directories can be git repositories
return unless (-d $_);
+ # don't traverse too deep (Find is super slow on os x)
+ if (($File::Find::name =~ tr!/!!) - $pfxdepth > $project_maxdepth) {
+ $File::Find::prune = 1;
+ return;
+ }
my $subdir = substr($File::Find::name, $pfxlen + 1);
# we check related file in $projectroot
@@ -1211,7 +1588,7 @@ sub git_get_projects_list {
if (check_export_ok("$projectroot/$path")) {
my $pr = {
path => $path,
- owner => decode_utf8($owner),
+ owner => to_utf8($owner),
};
push @list, $pr;
(my $forks_path = $path) =~ s/\.git$//;
@@ -1223,12 +1600,12 @@ sub git_get_projects_list {
return @list;
}
-sub git_get_project_owner {
- my $project = shift;
- my $owner;
+our $gitweb_project_owner = undef;
+sub git_get_project_list_from_file {
- return undef unless $project;
+ return if (defined $gitweb_project_owner);
+ $gitweb_project_owner = {};
# read from file (url-encoded):
# 'git%2Fgit.git Linus+Torvalds'
# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
@@ -1240,13 +1617,25 @@ sub git_get_project_owner {
my ($pr, $ow) = split ' ', $line;
$pr = unescape($pr);
$ow = unescape($ow);
- if ($pr eq $project) {
- $owner = decode_utf8($ow);
- last;
- }
+ $gitweb_project_owner->{$pr} = to_utf8($ow);
}
close $fd;
}
+}
+
+sub git_get_project_owner {
+ my $project = shift;
+ my $owner;
+
+ return undef unless $project;
+
+ if (!defined $gitweb_project_owner) {
+ git_get_project_list_from_file();
+ }
+
+ if (exists $gitweb_project_owner->{$project}) {
+ $owner = $gitweb_project_owner->{$project};
+ }
if (!defined $owner) {
$owner = get_file_owner("$projectroot/$project");
}
@@ -1272,6 +1661,7 @@ sub git_get_last_activity {
my $age = time - $timestamp;
return ($age, age_string($age));
}
+ return (undef, undef);
}
sub git_get_references {
@@ -1515,6 +1905,7 @@ sub parse_commits {
($arg ? ($arg) : ()),
("--max-count=" . $maxcount),
("--skip=" . $skip),
+ @extra_options,
$commit_id,
"--",
($filename ? ($filename) : ())
@@ -1631,6 +2022,48 @@ sub parse_ls_tree_line ($;%) {
return wantarray ? %res : \%res;
}
+# generates _two_ hashes, references to which are passed as 2 and 3 argument
+sub parse_from_to_diffinfo {
+ my ($diffinfo, $from, $to, @parents) = @_;
+
+ if ($diffinfo->{'nparents'}) {
+ # combined diff
+ $from->{'file'} = [];
+ $from->{'href'} = [];
+ fill_from_file_info($diffinfo, @parents)
+ unless exists $diffinfo->{'from_file'};
+ for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+ $from->{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'};
+ if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
+ $from->{'href'}[$i] = href(action=>"blob",
+ hash_base=>$parents[$i],
+ hash=>$diffinfo->{'from_id'}[$i],
+ file_name=>$from->{'file'}[$i]);
+ } else {
+ $from->{'href'}[$i] = undef;
+ }
+ }
+ } else {
+ $from->{'file'} = $diffinfo->{'from_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'});
+ } else {
+ delete $from->{'href'};
+ }
+ }
+
+ $to->{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
+ if (!is_deleted($diffinfo)) { # file exists in result
+ $to->{'href'} = href(action=>"blob", hash_base=>$hash,
+ hash=>$diffinfo->{'to_id'},
+ file_name=>$to->{'file'});
+ } else {
+ delete $to->{'href'};
+ }
+}
+
## ......................................................................
## parse to array of hashes functions
@@ -1731,7 +2164,7 @@ sub get_file_owner {
}
my $owner = $gcos;
$owner =~ s/[,;].*$//;
- return decode_utf8($owner);
+ return to_utf8($owner);
}
## ......................................................................
@@ -1814,7 +2247,7 @@ sub git_header_html {
my $title = "$site_name";
if (defined $project) {
- $title .= " - " . decode_utf8($project);
+ $title .= " - " . to_utf8($project);
if (defined $action) {
$title .= "/$action";
if (defined $file_name) {
@@ -1866,9 +2299,17 @@ EOF
printf(''."\n",
esc_param($project), href(action=>"rss"));
+ printf(''."\n",
+ esc_param($project), href(action=>"rss",
+ extra_options=>"--no-merges"));
printf(''."\n",
esc_param($project), href(action=>"atom"));
+ printf(''."\n",
+ esc_param($project), href(action=>"atom",
+ extra_options=>"--no-merges"));
} else {
printf(''."\n",
@@ -1902,6 +2343,8 @@ EOF
}
print "\n";
}
+ print "\n";
+
my ($have_search) = gitweb_check_feature('search');
if ((defined $project) && ($have_search)) {
if (!defined $searchtext) {
@@ -1915,23 +2358,28 @@ EOF
} else {
$search_hash = "HEAD";
}
+ my $action = $my_uri;
+ my ($use_pathinfo) = gitweb_check_feature('pathinfo');
+ if ($use_pathinfo) {
+ $action .= "/$project";
+ } else {
+ $cgi->param("p", $project);
+ }
$cgi->param("a", "search");
$cgi->param("h", $search_hash);
- $cgi->param("p", $project);
- print $cgi->startform(-method => "get", -action => $my_uri) .
+ print $cgi->startform(-method => "get", -action => $action) .
"
\n\n";
+ }
+
my $alternate = 1;
my $patchno = 0;
foreach my $line (@{$difftree}) {
@@ -2374,7 +2861,7 @@ sub git_difftree_body {
fill_from_file_info($diff, @parents)
unless exists $diff->{'from_file'};
- if ($diff->{'to_id'} ne ('0' x 40)) {
+ if (!is_deleted($diff)) {
# file exists in the result (child) commit
print "
" .
$cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
@@ -2623,6 +3110,7 @@ sub git_difftree_body {
} # we should not encounter Unmerged (U) or Unknown (X) status
print "\n";
}
+ print "
" if $has_header;
print "
\n";
}
@@ -2687,48 +3175,37 @@ sub git_patchset_body {
# 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]);
+ # compact combined diff output can have some patches skipped
+ # find which patch (using pathname of result) we are at now
+ my $to_name;
+ if ($diff_header[0] =~ m!^diff --cc "?(.*)"?$!) {
+ $to_name = $1;
}
- if ($diffinfo->{'nparents'}) {
- # combined diff
- $from{'file'} = [];
- $from{'href'} = [];
- fill_from_file_info($diffinfo, @hash_parents)
- unless exists $diffinfo->{'from_file'};
- for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
- $from{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'};
- if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
- $from{'href'}[$i] = href(action=>"blob",
- hash_base=>$hash_parents[$i],
- hash=>$diffinfo->{'from_id'}[$i],
- file_name=>$from{'file'}[$i]);
- } else {
- $from{'href'}[$i] = undef;
- }
- }
- } else {
- $from{'file'} = $diffinfo->{'from_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'});
+
+ do {
+ # read and prepare patch information
+ if (ref($difftree->[$patch_idx]) eq "HASH") {
+ # pre-parsed (or generated by hand)
+ $diffinfo = $difftree->[$patch_idx];
} else {
- delete $from{'href'};
+ $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
}
- }
- $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
- if ($diffinfo->{'status'} ne "D") { # not deleted file
- $to{'href'} = href(action=>"blob", hash_base=>$hash,
- hash=>$diffinfo->{'to_id'},
- file_name=>$to{'file'});
- } else {
- delete $to{'href'};
- }
+
+ # check if current raw line has no patch (it got simplified)
+ if (defined $to_name && $to_name ne $diffinfo->{'to_file'}) {
+ print "
\n"; # class="patch"
+
+ $patch_idx++;
+ $patch_number++;
+ }
+ } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} ||
+ $patch_idx > $#$difftree);
+
+ # modifies %from, %to hashes
+ parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
+
# this is first patch for raw difftree line with $patch_idx index
# we index @$difftree array from 0, but number patches from 1
print "
\n" if (!$patch_number);
+
+ # for compact combined (--cc) format, with chunk and patch simpliciaction
+ # patchset might be empty, but there might be unprocessed raw lines
+ for ($patch_idx++ if $patch_number > 0;
+ $patch_idx < @$difftree;
+ $patch_idx++) {
+ # 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]);
+ }
+
+ # generate anchor for "patch" links in difftree / whatchanged part
+ print "
The commit messages and authorship information will be scanned for the given string.
+EOT
+ my ($have_grep) = gitweb_check_feature('grep');
+ if ($have_grep) {
+ print <grep
+
All files in the currently selected tree (HEAD unless you are explicitly browsing
+ a different one) are searched for the given
+regular expression
+(POSIX extended) and the matches are listed. On large
+trees, this search can take a while and put some strain on the server, so please use it with
+some consideration.
+EOT
+ }
+ print <author
Name and e-mail of the change author and date of birth of the patch will be scanned for the given string.
committer
@@ -4804,7 +5340,7 @@ sub git_feed {
# log/feed of current (HEAD) branch, log of given branch, history of file/directory
my $head = $hash || 'HEAD';
- my @commitlist = parse_commits($head, 150);
+ my @commitlist = parse_commits($head, 150, 0, undef, $file_name);
my %latest_commit;
my %latest_date;
@@ -4916,7 +5452,8 @@ XML
# get list of changed files
open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
- $co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
+ $co{'parent'} || "--root",
+ $co{'id'}, "--", (defined $file_name ? $file_name : ())
or next;
my @difftree = map { chomp; $_ } <$fd>;
close $fd