contrib/hooks/post-receive-email: remove cruft, $committer is not used
[gitweb.git] / contrib / hooks / update-paranoid
index fb2aca3628783fbd80d96cb029cb600fcad51a29..068fa3708331bbb7d451040715c8bba9623f8c7e 100644 (file)
@@ -102,6 +102,8 @@ my ($this_user) = getpwuid $<; # REAL_USER_ID
 my $repository_name;
 my %user_committer;
 my @allow_rules;
+my @path_rules;
+my %diff_cache;
 
 sub deny ($) {
        print STDERR "-Deny-    $_[0]\n" if $debug;
@@ -122,6 +124,13 @@ sub git_value (@) {
        open(T,'-|','git',@_); local $_ = <T>; chop; close T; $_;
 }
 
+sub match_string ($$) {
+       my ($acl_n, $ref) = @_;
+          ($acl_n eq $ref)
+       || ($acl_n =~ m,/$, && substr($ref,0,length $acl_n) eq $acl_n)
+       || ($acl_n =~ m,^\^, && $ref =~ m:$acl_n:);
+}
+
 sub parse_config ($$$$) {
        my $data = shift;
        local $ENV{GIT_DIR} = shift;
@@ -209,6 +218,42 @@ sub check_committers (@) {
        }
 }
 
+sub load_diff ($) {
+       my $base = shift;
+       my $d = $diff_cache{$base};
+       unless ($d) {
+               local $/ = "\0";
+               my %this_diff;
+               if ($base =~ /^0{40}$/) {
+                       open(T,'-|','git','ls-tree',
+                               '-r','--name-only','-z',
+                               $new) or return undef;
+                       while (<T>) {
+                               chop;
+                               $this_diff{$_} = 'A';
+                       }
+                       close T or return undef;
+               } else {
+                       open(T,'-|','git','diff-tree',
+                               '-r','--name-status','-z',
+                               $base,$new) or return undef;
+                       while (<T>) {
+                               my $op = $_;
+                               chop $op;
+
+                               my $path = <T>;
+                               chop $path;
+
+                               $this_diff{$path} = $op;
+                       }
+                       close T or return undef;
+               }
+               $d = \%this_diff;
+               $diff_cache{$base} = $d;
+       }
+       return $d;
+}
+
 deny "No GIT_DIR inherited from caller" unless $git_dir;
 deny "Need a ref name" unless $ref;
 deny "Refusing funny ref $ref" unless $ref =~ s,^refs/,,;
@@ -266,7 +311,19 @@ RULE:
                        s/\${user\.$k}/$v->[0]/g;
                }
 
-               if (/^([CDRU ]+)\s+for\s+([^\s]+)$/) {
+               if (/^([AMD ]+)\s+of\s+([^\s]+)\s+for\s+([^\s]+)\s+diff\s+([^\s]+)$/) {
+                       my ($ops, $pth, $ref, $bst) = ($1, $2, $3, $4);
+                       $ops =~ s/ //g;
+                       $pth =~ s/\\\\/\\/g;
+                       $ref =~ s/\\\\/\\/g;
+                       push @path_rules, [$ops, $pth, $ref, $bst];
+               } elsif (/^([AMD ]+)\s+of\s+([^\s]+)\s+for\s+([^\s]+)$/) {
+                       my ($ops, $pth, $ref) = ($1, $2, $3);
+                       $ops =~ s/ //g;
+                       $pth =~ s/\\\\/\\/g;
+                       $ref =~ s/\\\\/\\/g;
+                       push @path_rules, [$ops, $pth, $ref, $old];
+               } elsif (/^([CDRU ]+)\s+for\s+([^\s]+)$/) {
                        my $ops = $1;
                        my $ref = $2;
                        $ops =~ s/ //g;
@@ -300,13 +357,65 @@ foreach my $acl_entry (@allow_rules) {
        next unless $acl_ops =~ /^[CDRU]+$/; # Uhh.... shouldn't happen.
        next unless $acl_n;
        next unless $op =~ /^[$acl_ops]$/;
+       next unless match_string $acl_n, $ref;
+
+       # Don't test path rules on branch deletes.
+       #
+       grant "Allowed by: $acl_ops for $acl_n" if $op eq 'D';
+
+       # Aggregate matching path rules; allow if there aren't
+       # any matching this ref.
+       #
+       my %pr;
+       foreach my $p_entry (@path_rules) {
+               my ($p_ops, $p_n, $p_ref, $p_bst) = @$p_entry;
+               next unless $p_ref;
+               push @{$pr{$p_bst}}, $p_entry if match_string $p_ref, $ref;
+       }
+       grant "Allowed by: $acl_ops for $acl_n" unless %pr;
 
-       grant "Allowed by: $acl_ops for $acl_n"
-       if (
-          ($acl_n eq $ref)
-       || ($acl_n =~ m,/$, && substr($ref,0,length $acl_n) eq $acl_n)
-       || ($acl_n =~ m,^\^, && $ref =~ m:$acl_n:)
-       );
+       # Allow only if all changes against a single base are
+       # allowed by file path rules.
+       #
+       my @bad;
+       foreach my $p_bst (keys %pr) {
+               my $diff_ref = load_diff $p_bst;
+               deny "Cannot difference trees." unless ref $diff_ref;
+
+               my %fd = %$diff_ref;
+               foreach my $p_entry (@{$pr{$p_bst}}) {
+                       my ($p_ops, $p_n, $p_ref, $p_bst) = @$p_entry;
+                       next unless $p_ops =~ /^[AMD]+$/;
+                       next unless $p_n;
+
+                       foreach my $f_n (keys %fd) {
+                               my $f_op = $fd{$f_n};
+                               next unless $f_op;
+                               next unless $f_op =~ /^[$p_ops]$/;
+                               delete $fd{$f_n} if match_string $p_n, $f_n;
+                       }
+                       last unless %fd;
+               }
+
+               if (%fd) {
+                       push @bad, [$p_bst, \%fd];
+               } else {
+                       # All changes relative to $p_bst were allowed.
+                       #
+                       grant "Allowed by: $acl_ops for $acl_n diff $p_bst";
+               }
+       }
+
+       foreach my $bad_ref (@bad) {
+               my ($p_bst, $fd) = @$bad_ref;
+               print STDERR "\n";
+               print STDERR "Not allowed to make the following changes:\n";
+               print STDERR "(base: $p_bst)\n";
+               foreach my $f_n (sort keys %$fd) {
+                       print STDERR "  $fd->{$f_n} $f_n\n";
+               }
+       }
+       deny "You are not permitted to $op $ref";
 }
 close A;
 deny "You are not permitted to $op $ref";