Skip to content

Commit

Permalink
refex-expr
Browse files Browse the repository at this point in the history
  • Loading branch information
sitaramc committed Mar 5, 2013
1 parent c770546 commit 79714c9
Show file tree
Hide file tree
Showing 8 changed files with 390 additions and 42 deletions.
104 changes: 64 additions & 40 deletions src/VREF/refex-expr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
use strict;
use warnings;

# see bottom of this file for instructons and IMPORTANT WARNINGS!
# ----------------------------------------------------------------------

my $rule = $ARGV[7];
die "\n\nFATAL: GL_REFEX_EXPR_ doesn't exist\n(your admin probably forgot the rc file change needed for this to work)\n\n"
unless exists $ENV{"GL_REFEX_EXPR_" . $rule};
Expand All @@ -12,64 +15,85 @@ exit 0;

__END__
Documentation for the refex-expression evaluation feature
------------------------------------------------------------------------
IMPORTANT WARNINGS:
* has not been tested heavily
* SO PLEASE TEST YOUR SPECIFIC USE CASE THOROUGHLY!
* read the NOTES section below
* syntax and semantics are to be considered beta and may change as I find
better use cases
------------------------------------------------------------------------
First, make sure you have both the VREF and the trigger scripts
(src/VREF/refex-expr and src/lib/Gitolite/Triggers/RefexExpr.pm)
Refex expressions, like VREFs, are best used as additional "deny" rules, to
deny combinations that the normal ruleset cannot detect.
Next, add this to the ACCESS_2 list in the rc file:
To enable this, uncomment 'refex-expr' in the ENABLE list in the rc file.
'RefexExpr::access_2',
It allows you to say things like "don't allow users u3 and u4 to change the
Makefile in the master branch" (i.e., they can change any other file in
master, or the Makefile in any other branch, but not that specific combo).
For the rest, we'll use this example:
repo foo
RW+ = u1 u2 # line 1
* user u1 can push foo to some other branch, and anything else to the master
branch, but not foo to the master branch
RW+ master = u3 u4 # line 2
RW+ = u3 u4 # line 3
RW+ VREF/NAME/Makefile = u3 u4 # line 4
- master and VREF/NAME/Makefile = u3 u4 # line 5
* user u2 is allowed to push either 'doc/' or 'src/' but not both
Line 5 is a "refex expression". Here are the rules:
* for each refex in the expression ("master" and "VREF/NAME/Makefile" in
this example), a count is kept of the number of times the EXACT refex was
matched and allowed in the *normal* rules (here, lines 2 and 4) during
this push.
Here's the conf file extract:
* the expression is evaluated based on these counts. 0 is false, and
any non-zero is true (see more examples later). The truth value of the
expression determines whether the refex expression matched.
repo testing
RW+ master = u1 # line 1
RW+ = @all # line 2
You can use any logical or arithmetic expression using refexes as operands
and using these operators:
RW+ VREF/NAME/foo = u1
RW+ VREF/NAME/doc/ = u2
RW+ VREF/NAME/src/ = u2
not and or xor + - == -lt -gt -eq -le -ge -ne
# set up 2 refex expressions, named e1, e2
option refex-expr.e1 = master and VREF/NAME/foo
option refex-expr.e2 = VREF/NAME/doc/ and VREF/NAME/src/
Parens are not allowed. Precedence is as you might expect for those
operators. It's actually perl that is evaluating it (you can guess what
the '-lt' etc., get translated to) so if in doubt, check 'man perlop'.
# now deny users if the corresponding expression is true
- VREF/refex-expr/e1 = u1
- VREF/refex-expr/e2 = u2
* the refexes that form the terms of the expression (in this case, lines 2
and 4) MUST come before the expression itself (i.e., line 5).
Here are some IMPORTANT notes:
* note the words "EXACT refex was matched" above.
* You MUST place VREF/refex-expr rules at the end. (Only 'partial-copy', if
you use it, must come later).
Let's say you add "u3" to line 1. Then the refex expression in line 5
would never match for u3. This is because line 1 prevents line 2 from
matching (being more general *and* appearing earlier), so the count for
the "master" refex would be 0. If "master" is 0 (false), then "master and
<anything>" is also false.
* You MUST explicitly permit the refexes used in your refex expressions. If
you have more generic rules, the specific ones must come first.
(Same thing is you swap lines 2 and 3; i.e., put the "RW+ = ..." before
the "RW+ master = ...").
For example, without line 1, the refex recorded for user u1 will come from
line 2, (so it will be 'refs/.*'), and 'master' in the refex expressions
will never have a true value.
Put another way, the terms in the refex expression are refexes, not refs.
Merely pushing the master branch does not mean the count for "master"
increases; it has to *match* on a line that has "master" as the refex.
* (corollary) make sure you use the exact same refex in the expression as
you did on the original rule line. E.g., a missing slash at the end will
mess things up.
Here are some more examples:
* user u2 is allowed to push either 'doc/' or 'src/' but not both
* You can use any logical expression using refexes as operands and using
these operators:
repo foo
RW+ = u1 u2 u3
and not xor or
RW+ VREF/NAME/doc/ = u2
RW+ VREF/NAME/src/ = u2
- VREF/NAME/doc/ and VREF/NAME/src/ = u2
Parens are not allowed.
* user u3 is allowed to push at most 2 files to conf/
If a refex has passed, it will have a 'true' value, else it will be false.
repo foo
RW+ = u1 u2 u3
The result of the evaluation, after these substitutions, will be the
result of the refex-expr VREF.
RW+ VREF/NAME/conf/ = u3
- VREF/NAME/conf/ -gt 2 = u3
3 changes: 2 additions & 1 deletion src/lib/Gitolite/Rc.pm
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ BEGIN { $non_core = "
continuation-lines SYNTACTIC_SUGAR .
keysubdirs-as-groups SYNTACTIC_SUGAR .
macros SYNTACTIC_SUGAR .
refex-expr SYNTACTIC_SUGAR .
renice PRE_GIT .
Expand All @@ -388,7 +389,7 @@ BEGIN { $non_core = "
Mirroring PRE_GIT ::
Mirroring POST_GIT ::
RefexExpr ACCESS_2 ::
refex-expr ACCESS_2 RefexExpr::access_2
RepoUmask PRE_GIT ::
RepoUmask POST_CREATE ::
Expand Down
2 changes: 1 addition & 1 deletion src/lib/Gitolite/Triggers/RefexExpr.pm
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use warnings;

# track refexes passed and evaluate expressions on them
# ----------------------------------------------------------------------
# see instructions for use at the bottom of src/VREF/refex-expr
# see src/VREF/refex-expr for instructions and WARNINGS!

use Gitolite::Easy;

Expand Down
39 changes: 39 additions & 0 deletions src/syntactic-sugar/refex-expr
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# vim: syn=perl:

# "sugar script" (syntactic sugar helper) for gitolite3
# ----------------------------------------------------------------------
# see src/VREF/refex-expr for instructions and WARNINGS!

my $perm = qr(-|R|RW\+?C?D?M?);

my $seq = 1;
sub sugar_script {
my $lines = shift;

Gitolite::Common::dd ['lines', $lines];

# my @out = ();
for my $l (@$lines) {
push @out, $l;

# quick check
next unless $l =~ /^($perm) /;
# more detailed check
next unless $l =~ /^($perm) (\S.*) = (\S.*)$/;
my ($perm, $refexes, $users) = ($1, $2, $3);
next unless $refexes =~ / (and|not|or|xor|\+|-|==|-lt|-gt|-eq|-le|-ge|-ne) /;

print STDERR ">>>> $l\n";
pop @out; # we need to replace that last line

my @words = grep { $_ !~ /^(and|not|or|xor|\+|-|==|-lt|-gt|-eq|-le|-ge|-ne)$/ } split ' ', $refexes;
push @out, map { "RW+ $_ = $users" } @words;
push @out, "option refex-expr.sugar$seq = $refexes";
push @out, "$perm VREF/refex-expr/sugar$seq = $users";

$seq++;
}

Gitolite::Common::dd ['out', \@out];
return \@out;
}
62 changes: 62 additions & 0 deletions t/refex-expr-test-1
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/bash

# not part of the official test suite (yet); just some q&d testing

# to be run from ~/gitolite as ./$0

set -e
exec 3>&2
exec > /dev/null
exec 2> /dev/null
print2() { echo -n "$@" >&3; }
say2() { echo "$@" >&3; }
die() { echo FATAL: "$@" >&3; exit 1; }

export od=$PWD
export tmp=$(mktemp -d)
echo $tmp >&3
trap "rm -rf $tmp" 0
cd $tmp

print2 setting up...
( cd $od; t/reset )
echo "push @{ \$RC{ENABLE} }, 'refex-expr';" >> ~/.gitolite.rc
cat <<EOF >> ~/.gitolite/conf/gitolite.conf
repo r1
RW+ = u1 u2 # line 1
RW+ master = u3 u4 # line 2
RW+ = u3 u4 # line 3
RW+ VREF/NAME/Makefile = u3 u4 # line 4
- master and VREF/NAME/Makefile = u3 u4 # line 5
EOF
gitolite setup
say2 done

# ----------------------------------------------------------------------

rm -rf u1
git clone u1:r1 u1
cd u1
tsh 'tc f1'
git push u1:r1 master
tsh 'tc f2'
git push u2:r1 master
tsh 'tc f3'
git push u3:r1 master
tsh 'tc f4'
git push u4:r1 master
say2 everyone master no Makefile

tsh 'tc f5 Makefile'
git push u1:r1 master
tsh 'tc f5 Makefile'
git push u1:r1 master:m1
say2 u1 Makefile master

tsh 'tc f5 Makefile'
git push u3:r1 master && die u3 r1 master should have failed
git push u3:r1 master:m2
say2 u3 Makefile master fail m2 pass
60 changes: 60 additions & 0 deletions t/refex-expr-test-2
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/bin/bash

# not part of the official test suite (yet); just some q&d testing

# to be run from ~/gitolite as ./$0

set -e
exec 3>&2
exec > /dev/null
exec 2> /dev/null
print2() { echo -n "$@" >&3; }
say2() { echo "$@" >&3; }
die() { echo FATAL: "$@" >&3; exit 1; }

export od=$PWD
export tmp=$(mktemp -d)
echo $tmp >&3
trap "rm -rf $tmp" 0
cd $tmp

print2 setting up...
( cd $od; t/reset )
echo "push @{ \$RC{ENABLE} }, 'refex-expr';" >> ~/.gitolite.rc
cat <<EOF >> ~/.gitolite/conf/gitolite.conf
repo r2
RW+ = @all
RW+ VREF/NAME/doc/ = u2
RW+ VREF/NAME/src/ = u2
- VREF/NAME/doc/ and VREF/NAME/src/ = u2
EOF
gitolite setup
say2 done

# ----------------------------------------------------------------------

git clone u2:r2
cd r2

tsh 'tc aa'
git push origin master
say2 aa pass

mkdir doc src

tsh 'tc doc/d1'
git push origin master
say2 doc pass

tsh 'tc src/s1'
tsh 'tc src/s2'
git push origin master
say2 src src pass

tsh 'tc doc/d2 src/s3'
git push origin master && die 1
git push u1:r2 master
say2 doc src u2 fail u1 pass
55 changes: 55 additions & 0 deletions t/refex-expr-test-3
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash

# not part of the official test suite (yet); just some q&d testing

# to be run from ~/gitolite as ./$0

set -e
exec 3>&2
exec > /dev/null
exec 2> /dev/null
print2() { echo -n "$@" >&3; }
say2() { echo "$@" >&3; }
die() { echo FATAL: "$@" >&3; exit 1; }

export od=$PWD
export tmp=$(mktemp -d)
echo $tmp >&3
trap "rm -rf $tmp" 0
cd $tmp

print2 setting up...
( cd $od; t/reset )
echo "push @{ \$RC{ENABLE} }, 'refex-expr';" >> ~/.gitolite.rc
cat <<EOF >> ~/.gitolite/conf/gitolite.conf
repo r3
RW+ = u1 u2 u3
RW+ VREF/NAME/conf/ = u3
- VREF/NAME/conf/ -gt 2 = u3
EOF
gitolite setup
say2 done

# ----------------------------------------------------------------------

git clone u3:r3
cd r3

tsh 'tc aa'
git push origin master
say2 aa pass

mkdir doc conf

tsh 'tc doc/d1 doc/d2 doc/d3 doc/d4 conf/c1'
git push origin master
say2 4 doc 1 conf pass

tsh 'tc conf/c2 conf/c3 conf/c4'
git push origin master && die 1

git push u2:r3 master
say2 3 conf u3 fail u2 pass
Loading

0 comments on commit 79714c9

Please sign in to comment.