summaryrefslogtreecommitdiffstats
path: root/src/lib/Gitolite/Triggers
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Gitolite/Triggers')
-rw-r--r--src/lib/Gitolite/Triggers/Alias.pm128
-rw-r--r--src/lib/Gitolite/Triggers/AutoCreate.pm24
-rw-r--r--src/lib/Gitolite/Triggers/CpuTime.pm52
-rwxr-xr-xsrc/lib/Gitolite/Triggers/Kindergarten.pm99
-rw-r--r--src/lib/Gitolite/Triggers/Mirroring.pm256
-rw-r--r--src/lib/Gitolite/Triggers/Motd.pm29
-rw-r--r--src/lib/Gitolite/Triggers/RefexExpr.pm80
-rw-r--r--src/lib/Gitolite/Triggers/RepoUmask.pm62
-rw-r--r--src/lib/Gitolite/Triggers/Shell.pm66
-rw-r--r--src/lib/Gitolite/Triggers/TProxy.pm98
-rw-r--r--src/lib/Gitolite/Triggers/Writable.pm17
11 files changed, 911 insertions, 0 deletions
diff --git a/src/lib/Gitolite/Triggers/Alias.pm b/src/lib/Gitolite/Triggers/Alias.pm
new file mode 100644
index 0000000..adaceb5
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/Alias.pm
@@ -0,0 +1,128 @@
+package Gitolite::Triggers::Alias;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+# aliasing a repo to another
+# ----------------------------------------------------------------------
+
+=for usage
+
+Why:
+
+ We had an existing repo "foo" that lots of people use. We wanted to
+ rename it to "foo/code", so that related repos "foo/upstream" and
+ "foo/docs" (both containing stuff we did not want to put in "foo") could
+ also be made and then the whole thing would be structured nicely.
+
+ At the same time we did not want to *force* all the users to change the
+ name. At least git operations should still work with the old name,
+ although it is OK for "info" and other "commands" to display/require the
+ proper name (i.e., the new name).
+
+How:
+
+ * uncomment the line "Alias" in the "user-visible behaviour" section in the
+ rc file
+
+ * add a new variable REPO_ALIASES to the rc file, with entries like:
+
+ REPO_ALIASES =>
+ {
+ # if you need a more aggressive warning message than the default
+ WARNING => "Please change your URLs to use '%new'; '%old' will not work after XXXX-XX-XX",
+
+ # prefix mapping section
+ PREFIX_MAPS => {
+ # note: NO leading slash in keys or values below
+ 'var/lib/git/' => '',
+ 'var/opt/git/' => 'opt/',
+ },
+
+ # individual repo mapping section
+ 'foo' => 'foo/code',
+
+ # force users to change their URLs
+ 'bar' => '301/bar/code',
+ # a target repo starting with "301/" won't actually work;
+ # it will just produce an error message pointing the user
+ # to the new name. This allows admins to force users to
+ # fix their URLs.
+ },
+
+ If a prefix map is supplied, each key is checked (in *undefined* order),
+ and the *first* key which matches the prefix of the repo will be applied.
+ If more than one key matches (for example if you specify '/abc/def' as one
+ key, and '/abc' as another), it is undefined which will get picked up.
+
+ The result of this, (or the original repo name if no map was found), will
+ then be subject to the individual repo mappings. Since these are full
+ repo names, there is no possibility of multiple matches.
+
+Notes:
+
+ * only git operations (clone/fetch/push) are alias aware. Nothing else in
+ gitolite, such as all the gitolite commands etc., are alias-aware and will
+ always use/require the proper repo name.
+
+ * http mode has not been tested and will not be. If someone has the time to
+ test it and make it work please let me know.
+
+ * funnily enough, this even works with mirroring! That is, a master can
+ push a repo "foo" to a copy per its configuration, while the copy thinks
+ it is getting repo "bar" from the master per its configuration.
+
+ Just make sure to put the Alias::input line *before* the Mirroring::input
+ line in the rc file on the copy.
+
+ However, it will probably not work with redirected pushes unless you setup
+ the opposite alias ("bar" -> "foo") on master.
+=cut
+
+sub input {
+ my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
+ my $user = $ARGV[0] || '@all'; # user name is undocumented for now
+
+ if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /(?:$git_commands) '\/?(\S+)'$/ ) {
+ my $repo = $1;
+ ( my $norm = $repo ) =~ s/\.git$//; # normalised repo name
+
+ my $target = $norm;
+
+ # prefix maps first
+ my $pm = $rc{REPO_ALIASES}{PREFIX_MAPS} || {};
+ while (my($k, $v) = each %$pm) {
+ last if $target =~ s/^$k/$v/;
+ # no /i, /g, etc. by design
+ }
+
+ # individual repo map next
+ $target = $rc{REPO_ALIASES}{$target} || $target;
+
+ # undocumented; don't use without discussing on mailing list
+ $target = $target->{$user} if ref($target) eq 'HASH';
+
+ # if the repo name finally maps to empty, we bail, with no changes
+ return unless $target;
+
+ # we're done. Did we actually change anything?
+ return if $norm eq $target;
+
+ # if the new name starts with "301/", inform and abort
+ _die "please use '$target' instead of '$norm'" if $target =~ s(^301/)();
+ # otherwise print a warning and continue with the new name
+ my $wm = $rc{REPO_ALIASES}{WARNING} || "'%old' is an alias for '%new'";
+ $wm =~ s/%new/$target/g;
+ $wm =~ s/%old/$norm/g;
+ _warn $wm;
+
+ $ENV{SSH_ORIGINAL_COMMAND} =~ s/'\/?$repo'/'$target'/;
+ }
+
+}
+
+1;
diff --git a/src/lib/Gitolite/Triggers/AutoCreate.pm b/src/lib/Gitolite/Triggers/AutoCreate.pm
new file mode 100644
index 0000000..e1d977a
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/AutoCreate.pm
@@ -0,0 +1,24 @@
+package Gitolite::Triggers::AutoCreate;
+
+use strict;
+use warnings;
+
+# perl trigger set for stuff to do with auto-creating repos
+# ----------------------------------------------------------------------
+
+# to deny auto-create on read access, uncomment 'no-create-on-read' in the
+# ENABLE list in the rc file
+sub deny_R {
+ die "autocreate denied\n" if $_[3] and $_[3] eq 'R';
+ return;
+}
+
+# to deny auto-create on read *and* write, uncomment 'no-auto-create' in the
+# ENABLE list in the rc file. This means you can only create wild repos using
+# the 'create' command, (which needs to be enabled in the ENABLE list).
+sub deny_RW {
+ die "autocreate denied\n" if $_[3] and ( $_[3] eq 'R' or $_[3] eq 'W' );
+ return;
+}
+
+1;
diff --git a/src/lib/Gitolite/Triggers/CpuTime.pm b/src/lib/Gitolite/Triggers/CpuTime.pm
new file mode 100644
index 0000000..74b4217
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/CpuTime.pm
@@ -0,0 +1,52 @@
+package Gitolite::Triggers::CpuTime;
+
+use Time::HiRes;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+# cpu and elapsed times for gitolite+git operations
+# ----------------------------------------------------------------------
+# uncomment the appropriate lines in the rc file to enable this
+
+# Ideally, you will (a) write your own code with a different filename so later
+# gitolite upgrades won't overwrite your copy, (b) add appropriate variables
+# to the rc file, and (c) change your rc file to call your program instead.
+
+# ----------------------------------------------------------------------
+my $start_time;
+
+sub input {
+ _warn "something wrong with the invocation of CpuTime::input" if $ENV{GL_TID} ne $$;
+ $start_time = [ Time::HiRes::gettimeofday() ];
+}
+
+sub post_git {
+ _warn "something wrong with the invocation of CpuTime::post_git" if $ENV{GL_TID} ne $$;
+
+ my ( $trigger, $repo, $user, $aa, $ref, $verb ) = @_;
+ my ( $utime, $stime, $cutime, $cstime ) = times();
+ my $elapsed = Time::HiRes::tv_interval($start_time);
+
+ gl_log( 'times', $utime, $stime, $cutime, $cstime, $elapsed );
+
+ # now do whatever you want with the data; the following is just an example.
+
+ if ( my $limit = $rc{CPU_TIME_WARN_LIMIT} ) {
+ my $total = $utime + $cutime + $stime + $cstime;
+ # some code to send an email or whatever...
+ say2 "limit = $limit, actual = $total" if $total > $limit;
+ }
+
+ if ( $rc{DISPLAY_CPU_TIME} ) {
+ say2 "perf stats for $verb on repo '$repo':";
+ say2 " user CPU time: " . ( $utime + $cutime );
+ say2 " sys CPU time: " . ( $stime + $cstime );
+ say2 " elapsed time: " . $elapsed;
+ }
+}
+
+1;
diff --git a/src/lib/Gitolite/Triggers/Kindergarten.pm b/src/lib/Gitolite/Triggers/Kindergarten.pm
new file mode 100755
index 0000000..6274c3d
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/Kindergarten.pm
@@ -0,0 +1,99 @@
+package Gitolite::Triggers::Kindergarten;
+
+# http://www.great-quotes.com/quote/424177
+# "Doctor, it hurts when I do this."
+# "Then don't do that!"
+
+# Prevent various things that sensible people shouldn't be doing anyway. List
+# of things it prevents is at the end of the program.
+
+# If you were forced to enable this module because someone is *constantly*
+# doing things that need to be caught, consider getting rid of that person.
+# Because, really, who knows what *else* he/she is doing that can't be caught
+# with some clever bit of code?
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+my %active;
+sub active {
+ # in rc, you either see just 'Kindergarten' to activate all features, or
+ # 'Kindergarten U0 CREATOR' (i.e., a space sep list of features after the
+ # word Kindergarten) to activate only those named features.
+
+ # no features specifically activated; implies all of them are active
+ return 1 if not %active;
+ # else check if this specific feature is active
+ return 1 if $active{ +shift };
+
+ return 0;
+}
+
+my ( $verb, $repo, $cmd, $args );
+sub input {
+ # get the features to be activated, if supplied
+ while ( $_[0] ne 'INPUT' ) {
+ $active{ +shift } = 1;
+ }
+
+ # generally fill up variables you might use later
+ my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
+ if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /($git_commands) '\/?(\S+)'$/ ) {
+ $verb = $1;
+ $repo = $2;
+ } elsif ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^(\S+) (.*)$/ ) {
+ $cmd = $1;
+ $args = $2;
+ }
+
+ prevent_CREATOR($repo) if active('CREATOR') and $verb;
+ prevent_0(@ARGV) if active('U0') and @ARGV;
+}
+
+sub prevent_CREATOR {
+ my $repo = shift;
+ _die "'CREATOR' not allowed as part of reponame" if $repo =~ /\bCREATOR\b/;
+}
+
+sub prevent_0 {
+ my $user = shift;
+ _die "'0' is not a valid username" if $user eq '0';
+}
+
+1;
+
+__END__
+
+CREATOR
+
+ prevent literal 'CREATOR' from being part of a repo name
+
+ a quirk deep inside gitolite would let this config
+
+ repo foo/CREATOR/..*
+ C = ...
+
+ allow the creation of repos like "foo/CREATOR/bar", i.e., the word CREATOR is
+ literally used.
+
+ I consider this a totally pathological situation to check for. The worst that
+ can happen is someone ends up cluttering the server with useless repos.
+
+ One solution could be to prevent this only for wild repos, but I can't be
+ bothered to fine tune this, so this module prevents even normal repos from
+ having the literal CREATOR in them.
+
+ See https://groups.google.com/forum/#!topic/gitolite/cS34Vxix0Us for more.
+
+U0
+
+ prevent a user from being called literal '0'
+
+ Ideally we should prevent keydir/0.pub (or variants) from being created,
+ but for "Then don't do that" purposes it's enough to prevent the user from
+ logging in.
+
+ See https://groups.google.com/forum/#!topic/gitolite/F1IBenuSTZo for more.
diff --git a/src/lib/Gitolite/Triggers/Mirroring.pm b/src/lib/Gitolite/Triggers/Mirroring.pm
new file mode 100644
index 0000000..07b7f96
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/Mirroring.pm
@@ -0,0 +1,256 @@
+package Gitolite::Triggers::Mirroring;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+my $hn = $rc{HOSTNAME};
+
+my ( $mode, $master, %copies, %trusted_copies );
+
+# ----------------------------------------------------------------------
+
+sub input {
+ unless ( $ARGV[0] =~ /^server-(\S+)$/ ) {
+ _die "'$ARGV[0]' is not a valid server name" if $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/;
+ return;
+ }
+
+ # note: we treat %rc as our own internal "poor man's %ENV"
+ $rc{FROM_SERVER} = $1;
+ trace( 3, "from_server: $1" );
+ my $sender = $rc{FROM_SERVER} || '';
+
+ # custom peer-to-peer commands. At present the only one is 'perms -c',
+ # sent from a mirror command
+ if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^CREATOR=(\S+) perms -c '(\S+)'$/ ) {
+ $ENV{GL_USER} = $1;
+
+ my $repo = $2;
+ details($repo);
+ _die "$hn: '$repo' is local" if $mode eq 'local';
+ _die "$hn: '$repo' is native" if $mode eq 'master';
+ _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
+
+ $ENV{GL_BYPASS_CREATOR_CHECK} = option($repo, "bypass-creator-check");
+ # this expects valid perms content on STDIN
+ _system("gitolite perms -c $repo");
+ delete $ENV{GL_BYPASS_CREATOR_CHECK};
+
+ # we're done. Yes, really...
+ exit 0;
+ }
+
+ if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/ ) {
+ # my ($user, $newsoc, $repo) = ($1, $2, $3);
+ $ENV{SSH_ORIGINAL_COMMAND} = $2;
+ @ARGV = ($1);
+ $rc{REDIRECTED_PUSH} = 1;
+ trace( 3, "redirected_push for user $1" );
+ } else {
+ # master -> copy push, no access checks needed
+ $ENV{GL_BYPASS_ACCESS_CHECKS} = 1;
+ }
+}
+
+# ----------------------------------------------------------------------
+
+sub pre_git {
+ return unless $hn;
+ # nothing, and I mean NOTHING, happens if HOSTNAME is not set
+ trace( 3, "pre_git() on $hn" );
+
+ my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
+
+ my $sender = $rc{FROM_SERVER} || '';
+ $user = '' if $sender and not exists $rc{REDIRECTED_PUSH};
+
+ # ------------------------------------------------------------------
+ # now you know the repo, get its mirroring details
+ details($repo);
+
+ # print mirror status if at least one copy status file is present
+ print_status( $repo ) if not $rc{HUSH_MIRROR_STATUS} and $mode ne 'local' and glob("$rc{GL_REPO_BASE}/$repo.git/gl-copy-*.status");
+
+ # we don't deal with any reads. Note that for pre-git this check must
+ # happen *after* getting details, to give mode() a chance to die on "known
+ # unknown" repos (repos that are in the config, but mirror settings
+ # exclude this host from both the master and copy lists)
+ return if $aa eq 'R';
+
+ trace( 1, "mirror", "pre_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
+
+ # ------------------------------------------------------------------
+ # case 1: we're master or copy, normal user pushing to us
+ if ( $user and not $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 1, user push" );
+ return if $mode eq 'local' or $mode eq 'master';
+ if ( $trusted_copies{$hn} ) {
+ trace( 1, "redirect to $master" );
+ exec( "ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
+ } else {
+ _die "$hn: pushing '$repo' to copy '$hn' not allowed";
+ }
+ }
+
+ # ------------------------------------------------------------------
+ # case 2: we're copy, master pushing to us
+ if ( $sender and not $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 2, master push" );
+ _die "$hn: '$repo' is local" if $mode eq 'local';
+ _die "$hn: '$repo' is native" if $mode eq 'master';
+ _die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
+ return;
+ }
+
+ # ------------------------------------------------------------------
+ # case 3: we're master, copy sending a redirected push to us
+ if ( $sender and $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 2, copy redirect" );
+ _die "$hn: '$repo' is local" if $mode eq 'local';
+ _die "$hn: '$repo' is not native" if $mode eq 'copy';
+ _die "$hn: '$sender' is not a valid copy for '$repo'" if not $copies{$sender};
+ _die "$hn: redirection not allowed from '$sender'" if not $trusted_copies{$sender};
+ return;
+ }
+
+ _die "$hn: should not reach this line";
+
+}
+
+# ----------------------------------------------------------------------
+
+sub post_git {
+ return unless $hn;
+ # nothing, and I mean NOTHING, happens if HOSTNAME is not set
+ trace( 1, "post_git() on $hn" );
+
+ my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
+ # we don't deal with any reads
+ return if $aa eq 'R';
+
+ my $sender = $rc{FROM_SERVER} || '';
+ $user = '' if $sender;
+
+ # ------------------------------------------------------------------
+ # now you know the repo, get its mirroring details
+ details($repo);
+
+ trace( 1, "mirror", "post_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
+
+ # ------------------------------------------------------------------
+ # case 1: we're master or copy, normal user pushing to us
+ if ( $user and not $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 1, user push" );
+ return if $mode eq 'local';
+ # copy was eliminated earlier anyway, so that leaves 'master'
+
+ # find all copies and push to each of them
+ push_to_copies($repo);
+
+ return;
+ }
+
+ # ------------------------------------------------------------------
+ # case 2: we're copy, master pushing to us
+ if ( $sender and not $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 2, master push" );
+ # nothing to do
+ return;
+ }
+
+ # ------------------------------------------------------------------
+ # case 3: we're master, copy sending a redirected push to us
+ if ( $sender and $rc{REDIRECTED_PUSH} ) {
+ trace( 3, "case 2, copy redirect" );
+
+ # find all copies and push to each of them
+ push_to_copies($repo);
+
+ return;
+ }
+}
+
+{
+ my $lastrepo = '';
+
+ sub details {
+ my $repo = shift;
+ return if $lastrepo eq $repo;
+
+ $master = master($repo);
+ %copies = copies($repo);
+ $mode = mode($repo);
+ %trusted_copies = trusted_copies($repo);
+ trace( 3, $master, $mode, join( ",", sort keys %copies ), join( ",", sort keys %trusted_copies ) );
+ }
+
+ sub master {
+ return option( +shift, 'mirror.master' );
+ }
+
+ sub copies {
+ my $repo = shift;
+
+ my $ref = git_config( $repo, "^gitolite-options\\.mirror\\.copies.*" );
+ my %out = map { $_ => 'async' } map { split } values %$ref;
+
+ $ref = git_config( $repo, "^gitolite-options\\.mirror\\.copies\\.sync.*" );
+ map { $out{$_} = 'sync' } map { split } values %$ref;
+
+ $ref = git_config( $repo, "^gitolite-options\\.mirror\\.copies\\.nosync.*" );
+ map { $out{$_} = 'nosync' } map { split } values %$ref;
+
+ return %out;
+ }
+
+ sub trusted_copies {
+ my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.redirectOK.*" );
+ # the list of trusted copies (where we accept redirected pushes from)
+ # is either explicitly given...
+ my @out = map { split } values %$ref;
+ my %out = map { $_ => 1 } @out;
+ # ...or it's all the copies mentioned if the list is just a "all"
+ %out = %copies if ( @out == 1 and $out[0] eq 'all' );
+ return %out;
+ }
+
+ sub mode {
+ my $repo = shift;
+ return 'local' if not $hn;
+ return 'master' if $master eq $hn;
+ return 'copy' if $copies{$hn};
+ return 'local' if not $master and not %copies;
+ _die "$hn: '$repo' is mirrored but not here";
+ }
+}
+
+sub push_to_copies {
+ my $repo = shift;
+
+ my $u = $ENV{GL_USER};
+ delete $ENV{GL_USER}; # why? see src/commands/mirror
+
+ my $lb = "$ENV{GL_REPO_BASE}/$repo.git/.gl-mirror-lock";
+ for my $s ( sort keys %copies ) {
+ trace( 1, "push_to_copies skipping self" ), next if $s eq $hn;
+ system("gitolite 1plus1 $lb.$s gitolite mirror push $s $repo </dev/null >/dev/null 2>&1 &") if $copies{$s} eq 'async';
+ system("gitolite 1plus1 $lb.$s gitolite mirror push $s $repo </dev/null >/dev/null 2>&1") if $copies{$s} eq 'sync';
+ _warn "manual mirror push pending for '$s'" if $copies{$s} eq 'nosync';
+ }
+
+ $ENV{GL_USER} = $u;
+}
+
+sub print_status {
+ my $repo = shift;
+ my $u = $ENV{GL_USER};
+ delete $ENV{GL_USER};
+ system("gitolite mirror status all $repo >&2");
+ $ENV{GL_USER} = $u;
+}
+
+1;
diff --git a/src/lib/Gitolite/Triggers/Motd.pm b/src/lib/Gitolite/Triggers/Motd.pm
new file mode 100644
index 0000000..6de80a2
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/Motd.pm
@@ -0,0 +1,29 @@
+package Gitolite::Triggers::Motd;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+# print a message of the day to STDERR
+# ----------------------------------------------------------------------
+
+my $file = "gl-motd";
+
+sub input {
+ # at present, we print it for every single interaction with gitolite. We
+ # may want to change that later; if we do, get code from Kindergarten.pm
+ # to get the gitcmd+repo or cmd+args so you can filter on them
+
+ my $f = "$rc{GL_ADMIN_BASE}/$file";
+ print STDERR slurp($f) if -f $f;
+}
+
+sub pre_git {
+ my $repo = $_[1];
+ my $f = "$rc{GL_REPO_BASE}/$repo.git/$file";
+ print STDERR slurp($f) if -f $f;
+}
+
+1;
diff --git a/src/lib/Gitolite/Triggers/RefexExpr.pm b/src/lib/Gitolite/Triggers/RefexExpr.pm
new file mode 100644
index 0000000..e913665
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/RefexExpr.pm
@@ -0,0 +1,80 @@
+package Gitolite::Triggers::RefexExpr;
+use strict;
+use warnings;
+
+# track refexes passed and evaluate expressions on them
+# ----------------------------------------------------------------------
+# see src/VREF/refex-expr for instructions and WARNINGS!
+
+use Gitolite::Easy;
+
+my %passed;
+my %rules;
+my $init_done = 0;
+
+sub access_2 {
+ # get out quick for repos that don't have any rules
+ return if $init_done and not %rules;
+
+ # but we don't really know that the first time, heh!
+ if ( not $init_done ) {
+ my $repo = $_[1];
+ init($repo);
+ return unless %rules;
+ }
+
+ my $refex = $_[5];
+ return if $refex =~ /DENIED/;
+
+ $passed{$refex}++;
+
+ # evaluate the rules each time; it's not very expensive
+ for my $k ( sort keys %rules ) {
+ $ENV{ "GL_REFEX_EXPR_" . $k } = eval_rule( $rules{$k} );
+ }
+}
+
+sub eval_rule {
+ my $rule = shift;
+
+ my $e;
+ $e = join " ", map { convert($_) } split ' ', $rule;
+
+ my $ret = eval $e;
+ _die "eval '$e' -> '$@'" if $@;
+ Gitolite::Common::trace( 1, "RefexExpr", "'$rule' -> '$e' -> '$ret'" );
+
+ return "'$rule' -> '$e'" if $ret;
+}
+
+my %constant;
+%constant = map { $_ => $_ } qw(1 not and or xor + - ==);
+$constant{'-lt'} = '<';
+$constant{'-gt'} = '>';
+$constant{'-eq'} = '==';
+$constant{'-le'} = '<=';
+$constant{'-ge'} = '>=';
+$constant{'-ne'} = '!=';
+
+sub convert {
+ my $i = shift;
+ return $i if $i =~ /^-?\d+$/;
+ return $constant{$i} || $passed{$i} || $passed{"refs/heads/$i"} || 0;
+}
+
+# called only once
+sub init {
+ $init_done = 1;
+ my $repo = shift;
+
+ # find all the rule expressions
+ my %t = config( $repo, "^gitolite-options\\.refex-expr\\." );
+ my ( $k, $v );
+ # get rid of the cruft and store just the rule name as the key
+ while ( ( $k, $v ) = each %t ) {
+ $k =~ s/^gitolite-options\.refex-expr\.//;
+ $rules{$k} = $v;
+ }
+}
+
+1;
diff --git a/src/lib/Gitolite/Triggers/RepoUmask.pm b/src/lib/Gitolite/Triggers/RepoUmask.pm
new file mode 100644
index 0000000..276cd01
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/RepoUmask.pm
@@ -0,0 +1,62 @@
+package Gitolite::Triggers::RepoUmask;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+# setting a repo specific umask
+# ----------------------------------------------------------------------
+# this is for people who are too paranoid to trust e.g., gitweb's repo
+# exclusion logic, but not paranoid enough to put it on a different server
+
+=for usage
+
+ * In the rc file, add the line
+ 'RepoUmask',
+ somewhere in the ENABLE list
+
+ * For each repo that is to get a different umask than the default, add a
+ line like this:
+
+ option umask = 0027
+
+ * Anytime you add or change the value, if there are existing repos that
+ would be affected, you will need to do a manual "chmod" adjustment,
+ because umask only affects newly created files.
+
+=cut
+
+# sadly option/config values are not available at pre_create time for normal
+# repos. So we have to do a one-time fixup in a post_create trigger.
+sub post_create {
+ my $repo = $_[1];
+
+ my $umask = option( $repo, 'umask' );
+ _chdir( $rc{GL_REPO_BASE} ); # because using option() moves us to ADMIN_BASE!
+
+ return unless $umask;
+
+ # unlike the one in the rc file, this is a string
+ $umask = oct($umask);
+ my $mode = "0" . sprintf( "%o", $umask ^ 0777 );
+
+ system("chmod -R $mode $repo.git >&2");
+ system("find $repo.git -type f -exec chmod a-x '{}' \\;");
+}
+
+sub pre_git {
+ my $repo = $_[1];
+
+ my $umask = option( $repo, 'umask' );
+ _chdir( $rc{GL_REPO_BASE} ); # because using option() moves us to ADMIN_BASE!
+
+ return unless $umask;
+
+ # unlike the one in the rc file, this is a string
+ umask oct($umask);
+}
+
+1;
diff --git a/src/lib/Gitolite/Triggers/Shell.pm b/src/lib/Gitolite/Triggers/Shell.pm
new file mode 100644
index 0000000..a2c5c0d
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/Shell.pm
@@ -0,0 +1,66 @@
+package Gitolite::Triggers::Shell;
+
+# usage notes: uncomment 'Shell' in the ENABLE list in the rc file.
+
+# documentation is in the ssh troubleshooting and tips document, under the
+# section "giving shell access to gitolite users"
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+# fedora likes to do things that are a little off the beaten track, compared
+# to typical gitolite usage:
+# - every user has their own login
+# - the forced command may not get the username as an argument. If it does
+# not, the gitolite user name is $USER (the unix user name)
+# - and finally, if the first argument to the forced command is '-s', and
+# $SSH_ORIGINAL_COMMAND is empty or runs a non-git/gitolite command, then
+# the user gets a shell
+
+sub input {
+ my $shell_allowed = 0;
+ if ( @ARGV and $ARGV[0] eq '-s' ) {
+ shift @ARGV;
+ $shell_allowed++;
+ }
+
+ @ARGV = ( $ENV{USER} ) unless @ARGV;
+
+ return unless $shell_allowed;
+
+ # now determine if this was intended as a shell command or git/gitolite
+ # command
+
+ my $soc = $ENV{SSH_ORIGINAL_COMMAND};
+
+ # no command, just 'ssh alice@host'; doesn't return ('exec's out)
+ shell_out() if $shell_allowed and not $soc;
+
+ return if git_gitolite_command($soc);
+
+ gl_log( 'shell', $ENV{SHELL}, "-c", $soc );
+ exec $ENV{SHELL}, "-c", $soc;
+}
+
+sub shell_out {
+ my $shell = $ENV{SHELL};
+ $shell =~ s/.*\//-/; # change "/bin/bash" to "-bash"
+ gl_log( 'shell', $shell );
+ exec { $ENV{SHELL} } $shell;
+}
+
+# some duplication with gitolite-shell, factor it out later, if it works fine
+# for fedora and they like it.
+sub git_gitolite_command {
+ my $soc = shift;
+
+ my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
+ return 1 if $soc =~ /^($git_commands) /;
+
+ my @words = split ' ', $soc;
+ return 1 if $rc{COMMANDS}{ $words[0] };
+
+ return 0;
+}
+
+1;
diff --git a/src/lib/Gitolite/Triggers/TProxy.pm b/src/lib/Gitolite/Triggers/TProxy.pm
new file mode 100644
index 0000000..9c42918
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/TProxy.pm
@@ -0,0 +1,98 @@
+package Gitolite::Triggers::TProxy;
+
+# ----------------------------------------------------------------------
+# transparent proxy for git repos, hosted on a gitolite server
+
+# ----------------------------------------------------------------------
+# WHAT
+
+# 1. user runs a git command (clone, fetch, push) against a gitolite
+# server.
+# 2. if that server has the repo, it will serve it up. Else it will
+# *transparently* forward the git operation to a designated upstream
+# server. The user does not have to do anything, and in fact may not
+# even know this has happened.
+
+# can be combined with, but does not *require*, gitolite mirroring.
+
+# ----------------------------------------------------------------------
+# SECURITY
+#
+# 1. Most of the issues that apply to "redirected push" in mirroring.html
+# also apply here. In particular, you had best make sure the two
+# servers use the same authentication data (i.e., "alice" here should be
+# "alice" there!)
+#
+# 2. Also, do not add keys for servers you don't trust!
+
+# ----------------------------------------------------------------------
+# HOW
+
+# on transparent proxy server (the one that is doing the redirect):
+# 1. add
+# INPUT => ['TProxy::input'],
+# just before the ENABLE list in the rc file
+# 2. add an RC variable to tell gitolite where to go; this is also just
+# before the ENABLE list:
+# TPROXY_FORWARDS_TO => 'git@upstream',
+
+# on upstream server (the one redirected TO):
+# 1. add
+# INPUT => ['TProxy::input'],
+# just before the ENABLE list in the rc file
+# 2. add the pubkey of the proxy server (the one that will be redirecting
+# to us) to this server's gitolite-admin "keydir" as
+# "server-<something>.pub", and push the change.
+
+# to use in combination with gitolite mirroring
+# 1. just follow the same instructions as above. Server names and
+# corresponding pub keys would already be set ok so step 2 in the
+# upstream server setup (above) will not be needed.
+# 2. needless to say, **don't** declare the repos you want to be
+# transparently proxied in the gitolite.conf for the copy.
+
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+my $soc = $ENV{SSH_ORIGINAL_COMMAND};
+
+# ----------------------------------------------------------------------
+
+sub input {
+ # are we the upstream, getting something from a tproxy server?
+ my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
+ if ( $ARGV[0] =~ /^server-/ and $soc =~ /^TPROXY_FOR=(\S+) SOC=(($git_commands) '\S+')$/ ) {
+ @ARGV = ($1);
+ # you better make sure you read the security warnings up there!
+
+ $ENV{SSH_ORIGINAL_COMMAND} = $2;
+ delete $ENV{GL_BYPASS_ACCESS_CHECKS};
+ # just in case we somehow end up running before Mirroring::input!
+
+ return;
+ }
+
+ # well we're not upstream; are we a tproxy?
+ return unless $rc{TPROXY_FORWARDS_TO};
+
+ # is it a normal git command?
+ return unless $ENV{SSH_ORIGINAL_COMMAND} =~ m(^($git_commands) '/?(.*?)(?:\.git(\d)?)?'$);
+
+ # ...get the repo name from $ENV{SSH_ORIGINAL_COMMAND}
+ my ( $verb, $repo, $trace_level ) = ( $1, $2, $3 );
+ $ENV{D} = $trace_level if $trace_level;
+ _die "invalid repo name: '$repo'" if $repo !~ $REPONAME_PATT;
+
+ # nothing to do if the repo exists locally
+ return if -d "$ENV{GL_REPO_BASE}/$repo.git";
+
+ my $user = shift @ARGV;
+ # redirect to upstream
+ exec( "ssh", $rc{TPROXY_FORWARDS_TO}, "TPROXY_FOR=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
+}
+
+1;
diff --git a/src/lib/Gitolite/Triggers/Writable.pm b/src/lib/Gitolite/Triggers/Writable.pm
new file mode 100644
index 0000000..ed86e12
--- /dev/null
+++ b/src/lib/Gitolite/Triggers/Writable.pm
@@ -0,0 +1,17 @@
+package Gitolite::Triggers::Writable;
+
+use Gitolite::Rc;
+use Gitolite::Common;
+
+sub access_1 {
+ my ( $repo, $aa, $result ) = @_[ 1, 3, 5 ];
+ return if $aa eq 'R' or $result =~ /DENIED/;
+
+ for my $f ( "$ENV{HOME}/.gitolite.down", "$rc{GL_REPO_BASE}/$repo.git/.gitolite.down" ) {
+ next unless -f $f;
+ _die slurp($f) if -s $f;
+ _die "sorry, writes are currently disabled (no more info available)\n";
+ }
+}
+
+1;