diff --git a/src/Gitolite/Rc.pm b/src/Gitolite/Rc.pm index 001c4d7..49ca35d 100644 --- a/src/Gitolite/Rc.pm +++ b/src/Gitolite/Rc.pm @@ -231,6 +231,7 @@ __DATA__ { 'help' => 1, 'info' => 1, + 'perms' => 1, }, ); diff --git a/src/commands/perms b/src/commands/perms new file mode 100755 index 0000000..e70ca81 --- /dev/null +++ b/src/commands/perms @@ -0,0 +1,89 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use lib $ENV{GL_BINDIR}; +use Gitolite::Rc; +use Gitolite::Common; +use Gitolite::Conf::Load; + +=for usage +Usage: ssh git@host perms -l + ssh git@host perms - + ssh git@host perms + + +List or set permissions for user-created ("wild") repo. The first usage shown +will list the current contents of the permissions file. The other two will +change permissions, adding or removing a user from a role. + +Examples: + ssh git@host perms foo + READERS user1 + ssh git@host perms foo + READERS user2 + ssh git@host perms foo + READERS user3 + +(Note: a legacy mode of piping in the entire permissions text directly is also +supported. If you want to use it, don't mix it with the new "+/-" modes). +=cut + +usage() if not @ARGV or $ARGV[0] eq '-h'; + +my $list = 0; +if ( $ARGV[0] eq '-l' ) { + $list++; + shift; + getperms(@ARGV); # doesn't return +} + +setperms(@ARGV); + +# ---------------------------------------------------------------------- + +sub getperms { + my $repo = shift; + _die "repo '$repo' missing" if repo_missing($repo); + my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms"; + + print slurp($pf) if -f $pf; + + exit 0; +} + +sub setperms { + my $repo = shift; + _die "repo '$repo' missing" if repo_missing($repo); + my $pf = "$rc{GL_REPO_BASE}/$repo.git/gl-perms"; + + if ( not @_ ) { + # legacy mode; pipe data in + @ARGV = (); + _print( $pf, <> ); + exit; + } + + _die "Invalid syntax. Please re-run with '-h' for detailed usage" if @_ != 3; + my ( $op, $role, $user ) = @_; + _die "Invalid syntax. Please re-run with '-h' for detailed usage" if $op ne '+' and $op ne '-'; + _die "Invalid role '$role'; check the rc file" if not $rc{ROLES}{$role}; + + my $text = ''; + my @text = slurp($pf) if -f $pf; + + my $present = grep { $_ eq "$role $user\n" } @text; + + if ( $op eq '-' ) { + if ( not $present ) { + _warn "'$role $user' was not present in file"; + } else { + @text = grep { $_ ne "$role $user\n" } @text; + _print( $pf, @text ); + } + } else { + if ($present) { + _warn "'$role $user' already present in file"; + } else { + push @text, "$role $user\n"; + @text = sort @text; + _print( $pf, @text ); + } + } +} diff --git a/t/wild-1.t b/t/wild-1.t new file mode 100755 index 0000000..79085a2 --- /dev/null +++ b/t/wild-1.t @@ -0,0 +1,151 @@ +#!/usr/bin/perl +use strict; +use warnings; + +# this is hardcoded; change it if needed +use lib "src"; +use Gitolite::Test; + +# basic tests +# ---------------------------------------------------------------------- + +try "plan 43"; + +confreset;confadd ' + @prof = u1 + @TAs = u2 u3 + @students = u4 u5 u6 + + @gfoo = foo/CREATOR/a[0-9][0-9] + repo @gfoo + C = @all + RW+ = CREATOR + RW = WRITERS @TAs + R = READERS @prof +'; + +try "ADMIN_PUSH set1; !/FATAL/" or die text(); + +try " +# reasonably complex setup; we'll do everything from one repo though +cd .. + +# u1 create success +glt clone u1 file:///foo/u1/a01 +/Initialized empty Git repository in .*/foo/u1/a01.git// + +# u2 create success +glt clone u2 file:///foo/u2/a02 +/Initialized empty Git repository in .*/foo/u2/a02.git// + +# u4 tries to create u2 repo +glt clone u4 file:///foo/u2/a12 +/R any foo/u2/a12 u4 DENIED by fallthru/ + +# line anchored regexes +glt clone u4 file:///foo/u4/a1234 +/R any foo/u4/a1234 u4 DENIED by fallthru/ + +# u4 tries to create his own repo +glt clone u4 file:///foo/u4/a12 +/Initialized empty Git repository in .*/foo/u4/a12.git// +/warning: You appear to have cloned an empty repository./ + +# u4 push success +cd a12 +tc p-728 p-729 p-730 p-731 +glt push u4 origin master +/To file:///foo/u4/a12/ +/\\* \\[new branch\\] master -> master/ + +# u1 clone success +cd .. +glt clone u1 file:///foo/u4/a12 u1a12 +/Cloning into 'u1a12'.../ + +# u1 push fail +cd u1a12 +tc m-778 m-779 +glt push u1 origin +/W any foo/u4/a12 u1 DENIED by fallthru/ + +# u2 clone success +cd .. +glt clone u2 file:///foo/u4/a12 u2a12 +/Cloning into 'u2a12'.../ + +# u2 push success +cd u2a12 +tc s-708 s-709 +glt push u2 origin +/To file:///foo/u4/a12/ +/master -> master/ + +# u2 rewind fail +glt push u2 -f origin master^:master +/\\+ refs/heads/master foo/u4/a12 u2 DENIED by fallthru/ +/error: hook declined to update refs/heads/master/ +/To file:///foo/u4/a12/ +/\\[remote rejected\\] master\\^ -> master \\(hook declined\\)/ +/error: failed to push some refs to 'file:///foo/u4/a12'/ + +# u4 pull to sync up +cd ../a12 +glt pull u4 +/Fast-forward/ +/From file:///foo/u4/a12/ +/master -> origin/master/ + +# u4 rewind success +git reset --hard HEAD^ +glt push u4 -f +/To file:///foo/u4/a12/ +/\\+ .* master -> master \\(forced update\\)/ + +# u5 clone fail +cd .. +glt clone u5 file:///foo/u4/a12 u5a12 +/R any foo/u4/a12 u5 DENIED by fallthru/ + +glt perms u4 foo/u4/a12 + READERS u5 +glt perms u4 foo/u4/a12 + WRITERS u6 + +glt perms u4 -l foo/u4/a12 +"; + +cmp 'READERS u5 +WRITERS u6 +'; + +try " +# u5 clone success +glt clone u5 file:///foo/u4/a12 u5a12 +/Cloning into 'u5a12'.../ + +# u5 push fail +cd u5a12 +tc y-743 y-744 +glt push u5 +/W any foo/u4/a12 u5 DENIED by fallthru/ + + +# u6 clone success +cd .. +glt clone u6 file:///foo/u4/a12 u6a12 +/Cloning into 'u6a12'.../ + +# u6 push success +cd u6a12 +tc k-68 k-69 +glt push u6 file:///foo/u4/a12 +/To file:///foo/u4/a12/ +/master -> master/ + +# u6 rewind fail +glt push u6 -f file:///foo/u4/a12 master^:master +/\\+ refs/heads/master foo/u4/a12 u6 DENIED by fallthru/ +/error: hook declined to update refs/heads/master/ +/To file:///foo/u4/a12/ +/\\[remote rejected\\] master\\^ -> master \\(hook declined\\)/ +/error: failed to push some refs to 'file:///foo/u4/a12'/ +";