t7300: Basic tests for git-clean
[gitweb.git] / gitweb / gitweb.perl
index 27b5970bcadf21f2f0e294c8c34d9595f6288503..ba5cc43e5b7ed6c9e980c60202c062d010bf170a 100755 (executable)
@@ -19,7 +19,7 @@
 binmode STDOUT, ':utf8';
 
 BEGIN {
-       CGI->compile() if $ENV{MOD_PERL};
+       CGI->compile() if $ENV{'MOD_PERL'};
 }
 
 our $cgi = new CGI;
@@ -71,6 +71,10 @@ BEGIN
 # source of projects list
 our $projects_list = "++GITWEB_LIST++";
 
+# default order of projects list
+# valid values are none, project, descr, owner, and age
+our $default_projects_order = "project";
+
 # show repository only if this file exists
 # (only effective if this variable evaluates to true)
 our $export_ok = "++GITWEB_EXPORT_OK++";
@@ -176,8 +180,8 @@ BEGIN
        # 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.
+       # all the forks. If project list is taken from a file, forks have
+       # to be listed after the main project.
 
        # To enable system wide have in $GITWEB_CONFIG
        # $feature{'forks'}{'default'} = [1];
@@ -560,12 +564,6 @@ sub validate_refname {
        return $input;
 }
 
-# very thin wrapper for decode("utf8", $str, Encode::FB_DEFAULT);
-sub to_utf8 {
-       my $str = shift;
-       return decode("utf8", $str, Encode::FB_DEFAULT);
-}
-
 # quote unsafe chars, but keep the slash, even when it's not
 # correct, but quoted slashes look too horrible in bookmarks
 sub esc_param {
@@ -590,7 +588,7 @@ ($;%)
        my $str = shift;
        my %opts = @_;
 
-       $str = to_utf8($str);
+       $str = decode_utf8($str);
        $str = $cgi->escapeHTML($str);
        if ($opts{'-nbsp'}) {
                $str =~ s/ / /g;
@@ -604,7 +602,7 @@ sub esc_path {
        my $str = shift;
        my %opts = @_;
 
-       $str = to_utf8($str);
+       $str = decode_utf8($str);
        $str = $cgi->escapeHTML($str);
        if ($opts{'-nbsp'}) {
                $str =~ s/ / /g;
@@ -887,7 +885,7 @@ sub format_subject_html {
 
        if (length($short) < length($long)) {
                return $cgi->a({-href => $href, -class => "list subject",
-                               -title => to_utf8($long)},
+                               -title => decode_utf8($long)},
                       esc_html($short) . $extra);
        } else {
                return $cgi->a({-href => $href, -class => "list subject"},
@@ -1047,6 +1045,8 @@ sub git_get_projects_list {
        $filter ||= '';
        $filter =~ s/\.git$//;
 
+       my ($check_forks) = gitweb_check_feature('forks');
+
        if (-d $projects_list) {
                # search in directory
                my $dir = $projects_list . ($filter ? "/$filter" : '');
@@ -1054,8 +1054,6 @@ sub git_get_projects_list {
                $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
@@ -1081,7 +1079,9 @@ sub git_get_projects_list {
                # 'git%2Fgit.git Linus+Torvalds'
                # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
                # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
+               my %paths;
                open my ($fd), $projects_list or return;
+       PROJECT:
                while (my $line = <$fd>) {
                        chomp $line;
                        my ($path, $owner) = split ' ', $line;
@@ -1094,24 +1094,41 @@ sub git_get_projects_list {
                                # looking for forks;
                                my $pfx = substr($path, 0, length($filter));
                                if ($pfx ne $filter) {
-                                       next;
+                                       next PROJECT;
                                }
                                my $sfx = substr($path, length($filter));
                                if ($sfx !~ /^\/.*\.git$/) {
-                                       next;
+                                       next PROJECT;
+                               }
+                       } elsif ($check_forks) {
+                       PATH:
+                               foreach my $filter (keys %paths) {
+                                       # looking for forks;
+                                       my $pfx = substr($path, 0, length($filter));
+                                       if ($pfx ne $filter) {
+                                               next PATH;
+                                       }
+                                       my $sfx = substr($path, length($filter));
+                                       if ($sfx !~ /^\/.*\.git$/) {
+                                               next PATH;
+                                       }
+                                       # is a fork, don't include it in
+                                       # the list
+                                       next PROJECT;
                                }
                        }
                        if (check_export_ok("$projectroot/$path")) {
                                my $pr = {
                                        path => $path,
-                                       owner => to_utf8($owner),
+                                       owner => decode_utf8($owner),
                                };
-                               push @list, $pr
+                               push @list, $pr;
+                               (my $forks_path = $path) =~ s/\.git$//;
+                               $paths{$forks_path}++;
                        }
                }
                close $fd;
        }
-       @list = sort {$a->{'path'} cmp $b->{'path'}} @list;
        return @list;
 }
 
@@ -1133,7 +1150,7 @@ sub git_get_project_owner {
                        $pr = unescape($pr);
                        $ow = unescape($ow);
                        if ($pr eq $project) {
-                               $owner = to_utf8($ow);
+                               $owner = decode_utf8($ow);
                                last;
                        }
                }
@@ -1607,7 +1624,7 @@ sub get_file_owner {
        }
        my $owner = $gcos;
        $owner =~ s/[,;].*$//;
-       return to_utf8($owner);
+       return decode_utf8($owner);
 }
 
 ## ......................................................................
@@ -1690,7 +1707,7 @@ sub git_header_html {
 
        my $title = "$site_name";
        if (defined $project) {
-               $title .= " - " . to_utf8($project);
+               $title .= " - " . decode_utf8($project);
                if (defined $action) {
                        $title .= "/$action";
                        if (defined $file_name) {
@@ -1800,7 +1817,7 @@ sub git_header_html {
                      $cgi->hidden(-name => "a") . "\n" .
                      $cgi->hidden(-name => "h") . "\n" .
                      $cgi->popup_menu(-name => 'st', -default => 'commit',
-                                      -values => ['commit', 'author', 'committer', 'pickaxe']) .
+                                      -values => ['commit', 'author', 'committer', 'pickaxe']) .
                      $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
                      " search:\n",
                      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
@@ -1870,16 +1887,16 @@ sub git_print_page_nav {
        my %arg = map { $_ => {action=>$_} } @navs;
        if (defined $head) {
                for (qw(commit commitdiff)) {
-                       $arg{$_}{hash} = $head;
+                       $arg{$_}{'hash'} = $head;
                }
                if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
                        for (qw(shortlog log)) {
-                               $arg{$_}{hash} = $head;
+                               $arg{$_}{'hash'} = $head;
                        }
                }
        }
-       $arg{tree}{hash} = $treehead if defined $treehead;
-       $arg{tree}{hash_base} = $treebase if defined $treebase;
+       $arg{'tree'}{'hash'} = $treehead if defined $treehead;
+       $arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
 
        print "<div class=\"page_nav\">\n" .
                (join " | ",
@@ -1927,9 +1944,9 @@ sub git_print_header_div {
        my ($action, $title, $hash, $hash_base) = @_;
        my %args = ();
 
-       $args{action} = $action;
-       $args{hash} = $hash if $hash;
-       $args{hash_base} = $hash_base if $hash_base;
+       $args{'action'} = $action;
+       $args{'hash'} = $hash if $hash;
+       $args{'hash_base'} = $hash_base if $hash_base;
 
        print "<div class=\"header\">\n" .
              $cgi->a({-href => href(%args), -class => "title"},
@@ -1963,7 +1980,7 @@ sub git_print_page_path {
 
        print "<div class=\"page_path\">";
        print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
-                     -title => 'tree root'}, to_utf8("[$project]"));
+                     -title => 'tree root'}, decode_utf8("[$project]"));
        print " / ";
        if (defined $name) {
                my @dirname = split '/', $name;
@@ -2375,6 +2392,7 @@ sub git_patchset_body {
        my ($fd, $difftree, $hash, $hash_parent) = @_;
 
        my $patch_idx = 0;
+       my $patch_number = 0;
        my $patch_line;
        my $diffinfo;
        my (%from, %to);
@@ -2396,6 +2414,7 @@ sub git_patchset_body {
                # git diff header
                #assert($patch_line =~ m/^diff /) if DEBUG;
                #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
+               $patch_number++;
                push @diff_header, $patch_line;
 
                # extended diff header
@@ -2558,6 +2577,7 @@ sub git_patchset_body {
        } continue {
                print "</div>\n"; # class="patch"
        }
+       print "<div class=\"diff nodifferences\">No differences found</div>\n" if (!$patch_number);
 
        print "</div>\n"; # class="patchset"
 }
@@ -2578,7 +2598,7 @@ sub git_project_list_body {
                ($pr->{'age'}, $pr->{'age_string'}) = @aa;
                if (!defined $pr->{'descr'}) {
                        my $descr = git_get_project_description($pr->{'path'}) || "";
-                       $pr->{'descr_long'} = to_utf8($descr);
+                       $pr->{'descr_long'} = decode_utf8($descr);
                        $pr->{'descr'} = chop_str($descr, 25, 5);
                }
                if (!defined $pr->{'owner'}) {
@@ -2598,7 +2618,7 @@ sub git_project_list_body {
                push @projects, $pr;
        }
 
-       $order ||= "project";
+       $order ||= $default_projects_order;
        $from = 0 unless defined $from;
        $to = $#projects if (!defined $to || $#projects < $to);
 
@@ -2957,7 +2977,7 @@ sub git_search_grep_body {
 
 sub git_project_list {
        my $order = $cgi->param('o');
-       if (defined $order && $order !~ m/project|descr|owner|age/) {
+       if (defined $order && $order !~ m/none|project|descr|owner|age/) {
                die_error(undef, "Unknown order parameter");
        }
 
@@ -2980,7 +3000,7 @@ sub git_project_list {
 
 sub git_forks {
        my $order = $cgi->param('o');
-       if (defined $order && $order !~ m/project|descr|owner|age/) {
+       if (defined $order && $order !~ m/none|project|descr|owner|age/) {
                die_error(undef, "Unknown order parameter");
        }
 
@@ -3095,7 +3115,7 @@ sub git_summary {
                git_project_list_body(\@forklist, undef, 0, 15,
                                      $#forklist <= 15 ? undef :
                                      $cgi->a({-href => href(action=>"forks")}, "..."),
-                                     'noheader');
+                                     'noheader');
        }
 
        git_footer_html();
@@ -3154,7 +3174,7 @@ sub git_blame2 {
        }
        $ftype = git_get_type($hash);
        if ($ftype !~ "blob") {
-               die_error("400 Bad Request", "Object is not a blob");
+               die_error('400 Bad Request', "Object is not a blob");
        }
        open ($fd, "-|", git_cmd(), "blame", '-p', '--',
              $file_name, $hash_base)
@@ -3202,7 +3222,7 @@ sub git_blame2 {
                my $rev = substr($full_rev, 0, 8);
                my $author = $meta->{'author'};
                my %date = parse_date($meta->{'author-time'},
-                                     $meta->{'author-tz'});
+                                     $meta->{'author-tz'});
                my $date = $date{'iso-tz'};
                if ($group_size) {
                        $current_color = ++$current_color % $num_colors;
@@ -3214,24 +3234,24 @@ sub git_blame2 {
                        print " rowspan=\"$group_size\"" if ($group_size > 1);
                        print ">";
                        print $cgi->a({-href => href(action=>"commit",
-                                                    hash=>$full_rev,
-                                                    file_name=>$file_name)},
-                                     esc_html($rev));
+                                                    hash=>$full_rev,
+                                                    file_name=>$file_name)},
+                                     esc_html($rev));
                        print "</td>\n";
                }
                open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
-                       or die_error("could not open git-rev-parse");
+                       or die_error(undef, "Open git-rev-parse failed");
                my $parent_commit = <$dd>;
                close $dd;
                chomp($parent_commit);
                my $blamed = href(action => 'blame',
-                                 file_name => $meta->{'filename'},
-                                 hash_base => $parent_commit);
+                                 file_name => $meta->{'filename'},
+                                 hash_base => $parent_commit);
                print "<td class=\"linenr\">";
                print $cgi->a({ -href => "$blamed#l$orig_lineno",
-                               -id => "l$lineno",
-                               -class => "linenr" },
-                             esc_html($lineno));
+                               -id => "l$lineno",
+                               -class => "linenr" },
+                             esc_html($lineno));
                print "</td>";
                print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
                print "</tr>\n";
@@ -3610,7 +3630,7 @@ sub git_snapshot {
                $hash = git_get_head_hash($project);
        }
 
-       my $filename = to_utf8(basename($project)) . "-$hash.tar.$suffix";
+       my $filename = decode_utf8(basename($project)) . "-$hash.tar.$suffix";
 
        print $cgi->header(
                -type => "application/$ctype",
@@ -3621,8 +3641,8 @@ sub git_snapshot {
        my $name = $project;
        $name =~ s/\047/\047\\\047\047/g;
        open my $fd, "-|",
-       "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
-               or die_error(undef, "Execute git-tar-tree failed.");
+               "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
+               or die_error(undef, "Execute git-tar-tree failed");
        binmode STDOUT, ':raw';
        print <$fd>;
        binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
@@ -3719,7 +3739,7 @@ sub git_commit {
                $formats_nav .=
                        '(merge: ' .
                        join(' ', map {
-                               $cgi->a({-href => href(action=>"commitdiff",
+                               $cgi->a({-href => href(action=>"commit",
                                                       hash=>$_)},
                                        esc_html(substr($_, 0, 7)));
                        } @$parents ) .
@@ -3734,7 +3754,7 @@ sub git_commit {
                # difftree output is not printed for merges
                open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
                        @diff_opts, $parent, $hash, "--"
-                               or die_error(undef, "Open git-diff-tree failed");
+                       or die_error(undef, "Open git-diff-tree failed");
                @difftree = map { chomp; $_ } <$fd>;
                close $fd or die_error(undef, "Reading git-diff-tree failed");
        }
@@ -3885,7 +3905,7 @@ sub git_blobdiff {
                        # read raw output
                        open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
                                $hash_parent_base, $hash_base,
-                               "--", $file_name
+                               "--", (defined $file_parent ? $file_parent : ()), $file_name
                                or die_error(undef, "Open git-diff-tree failed");
                        @difftree = map { chomp; $_ } <$fd>;
                        close $fd
@@ -3934,8 +3954,9 @@ sub git_blobdiff {
 
                # open patch output
                open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
-                       '-p', $hash_parent_base, $hash_base,
-                       "--", $file_name
+                       '-p', ($format eq 'html' ? "--full-index" : ()),
+                       $hash_parent_base, $hash_base,
+                       "--", (defined $file_parent ? $file_parent : ()), $file_name
                        or die_error(undef, "Open git-diff-tree failed");
        }
 
@@ -3969,7 +3990,8 @@ sub git_blobdiff {
                }
 
                # open patch output
-               open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts,
+               open $fd, "-|", git_cmd(), "diff", @diff_opts,
+                       '-p', ($format eq 'html' ? "--full-index" : ()),
                        $hash_parent, $hash, "--"
                        or die_error(undef, "Open git-diff failed");
        } else  {
@@ -4304,13 +4326,13 @@ sub git_search {
                if ($page > 0) {
                        $paging_nav .=
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype)},
-                                       "first");
+                                                      searchtext=>$searchtext, searchtype=>$searchtype)},
+                                       "first");
                        $paging_nav .= " &sdot; " .
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype,
-                                                      page=>$page-1),
-                                        -accesskey => "p", -title => "Alt-p"}, "prev");
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page-1),
+                                        -accesskey => "p", -title => "Alt-p"}, "prev");
                } else {
                        $paging_nav .= "first";
                        $paging_nav .= " &sdot; prev";
@@ -4318,9 +4340,9 @@ sub git_search {
                if ($#commitlist >= 100) {
                        $paging_nav .= " &sdot; " .
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype,
-                                                      page=>$page+1),
-                                        -accesskey => "n", -title => "Alt-n"}, "next");
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page+1),
+                                        -accesskey => "n", -title => "Alt-n"}, "next");
                } else {
                        $paging_nav .= " &sdot; next";
                }
@@ -4328,9 +4350,9 @@ sub git_search {
                if ($#commitlist >= 100) {
                        $next_link =
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype,
-                                                      page=>$page+1),
-                                        -accesskey => "n", -title => "Alt-n"}, "next");
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page+1),
+                                        -accesskey => "n", -title => "Alt-n"}, "next");
                }
 
                git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);