gitweb: Simplify 'opt' parameter validation, add "no merges" feeds
[gitweb.git] / gitweb / gitweb.perl
index c4f88245aa23ccbab07cc109a8478f1dd9ed8fdb..8a32899655d1a2862e3a3ffbf6df560811bc493b 100755 (executable)
@@ -307,10 +307,6 @@ sub feature_snapshot {
 
        if ($val) {
                @fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val);
-               @fmts = grep { defined } map {
-                       exists $known_snapshot_format_aliases{$_} ?
-                               $known_snapshot_format_aliases{$_} : $_ } @fmts;
-               @fmts = grep(exists $known_snapshot_formats{$_}, @fmts);
        }
 
        return @fmts;
@@ -356,6 +352,18 @@ sub check_export_ok {
                (!$export_ok || -e "$dir/$export_ok"));
 }
 
+# process alternate names for backward compatibility
+# filter out unsupported (unknown) snapshot formats
+sub filter_snapshot_fmts {
+       my @fmts = @_;
+
+       @fmts = map {
+               exists $known_snapshot_format_aliases{$_} ?
+                      $known_snapshot_format_aliases{$_} : $_} @fmts;
+       @fmts = grep(exists $known_snapshot_formats{$_}, @fmts);
+
+}
+
 our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
 do $GITWEB_CONFIG if -e $GITWEB_CONFIG;
 
@@ -428,12 +436,11 @@ sub check_export_ok {
 
 our @extra_options = $cgi->param('opt');
 if (defined @extra_options) {
-       foreach(@extra_options)
-       {
-               if (not grep(/^$_$/, keys %allowed_options)) {
+       foreach my $opt (@extra_options) {
+               if (not exists $allowed_options{$opt}) {
                        die_error(undef, "Invalid option parameter");
                }
-               if (not grep(/^$action$/, @{$allowed_options{$_}})) {
+               if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
                        die_error(undef, "Invalid option parameter for this action");
                }
        }
@@ -590,7 +597,6 @@ (%)
                action => "a",
                file_name => "f",
                file_parent => "fp",
-               extra_options => "opt",
                hash => "h",
                hash_parent => "hp",
                hash_base => "hb",
@@ -600,6 +606,7 @@ (%)
                searchtext => "s",
                searchtype => "st",
                snapshot_format => "sf",
+               extra_options => "opt",
        );
        my %mapping = @mapping;
 
@@ -882,11 +889,25 @@ sub age_string {
        return $age_str;
 }
 
+use constant {
+       S_IFINVALID => 0030000,
+       S_IFGITLINK => 0160000,
+};
+
+# submodule/subproject, a commit object reference
+sub S_ISGITLINK($) {
+       my $mode = shift;
+
+       return (($mode & S_IFMT) == S_IFGITLINK)
+}
+
 # convert file mode in octal to symbolic file mode string
 sub mode_str {
        my $mode = oct shift;
 
-       if (S_ISDIR($mode & S_IFMT)) {
+       if (S_ISGITLINK($mode)) {
+               return 'm---------';
+       } elsif (S_ISDIR($mode & S_IFMT)) {
                return 'drwxr-xr-x';
        } elsif (S_ISLNK($mode)) {
                return 'lrwxrwxrwx';
@@ -912,7 +933,9 @@ sub file_type {
                $mode = oct $mode;
        }
 
-       if (S_ISDIR($mode & S_IFMT)) {
+       if (S_ISGITLINK($mode)) {
+               return "submodule";
+       } elsif (S_ISDIR($mode & S_IFMT)) {
                return "directory";
        } elsif (S_ISLNK($mode)) {
                return "symlink";
@@ -933,7 +956,9 @@ sub file_type_long {
                $mode = oct $mode;
        }
 
-       if (S_ISDIR($mode & S_IFMT)) {
+       if (S_ISGITLINK($mode)) {
+               return "submodule";
+       } elsif (S_ISDIR($mode & S_IFMT)) {
                return "directory";
        } elsif (S_ISLNK($mode)) {
                return "symlink";
@@ -1299,9 +1324,11 @@ sub format_diff_line {
 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(
@@ -1313,8 +1340,10 @@ sub format_snapshot_links {
                , @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({
+               return
+                       $cgi->a({
                                -href => href(
                                        action=>"snapshot",
                                        hash=>$hash,
@@ -2255,9 +2284,17 @@ sub git_header_html {
                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 RSS feed (no merges)" '.
+                      'href="%s" type="application/rss+xml" />'."\n",
+                      esc_param($project), href(action=>"rss",
+                                                extra_options=>"--no-merges"));
                printf('<link rel="alternate" title="%s log Atom feed" '.
                       'href="%s" type="application/atom+xml" />'."\n",
                       esc_param($project), href(action=>"atom"));
+               printf('<link rel="alternate" title="%s log Atom feed (no merges)" '.
+                      'href="%s" type="application/atom+xml" />'."\n",
+                      esc_param($project), href(action=>"atom",
+                                                extra_options=>"--no-merges"));
        } else {
                printf('<link rel="alternate" title="%s projects list" '.
                       'href="%s" type="text/plain; charset=utf-8"/>'."\n",
@@ -2695,6 +2732,20 @@ sub git_print_tree_entry {
                                      "history");
                }
                print "</td>\n";
+       } else {
+               # unknown object: we can only present history for it
+               # (this includes 'commit' object, i.e. submodule support)
+               print "<td class=\"list\">" .
+                     esc_path($t->{'name'}) .
+                     "</td>\n";
+               print "<td class=\"link\">";
+               if (defined $hash_base) {
+                       print $cgi->a({-href => href(action=>"history",
+                                                    hash_base=>$hash_base,
+                                                    file_name=>"$basedir$t->{'name'}")},
+                                     "history");
+               }
+               print "</td>\n";
        }
 }
 
@@ -4302,11 +4353,19 @@ sub git_tree {
 
 sub git_snapshot {
        my @supported_fmts = gitweb_check_feature('snapshot');
+       @supported_fmts = filter_snapshot_fmts(@supported_fmts);
 
        my $format = $cgi->param('sf');
-       unless ($format =~ m/[a-z0-9]+/
-               && exists($known_snapshot_formats{$format})
-               && grep($_ eq $format, @supported_fmts)) {
+       if (!@supported_fmts) {
+               die_error('403 Permission denied', "Permission denied");
+       }
+       # default to first supported snapshot format
+       $format ||= $supported_fmts[0];
+       if ($format !~ m/^[a-z0-9]+$/) {
+               die_error(undef, "Invalid snapshot format parameter");
+       } elsif (!exists($known_snapshot_formats{$format})) {
+               die_error(undef, "Unknown snapshot format");
+       } elsif (!grep($_ eq $format, @supported_fmts)) {
                die_error(undef, "Unsupported snapshot format");
        }
 
@@ -4323,7 +4382,7 @@ sub git_snapshot {
        my $cmd;
        $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}";
        $cmd = "$git_command archive " .
-               "--format=$known_snapshot_formats{$format}{'format'}" .
+               "--format=$known_snapshot_formats{$format}{'format'} " .
                "--prefix=\'$name\'/ $hash";
        if (exists $known_snapshot_formats{$format}{'compressor'}) {
                $cmd .= ' | ' . join ' ', @{$known_snapshot_formats{$format}{'compressor'}};