summaryrefslogtreecommitdiffstats
path: root/src/triggers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:17:27 +0000
commitaae1a14ea756102251351d96e2567b4986d30e2b (patch)
treea1af617672e26aee4c1031a3aa83e8ff08f6a0a5 /src/triggers
parentInitial commit. (diff)
downloadgitolite3-upstream.tar.xz
gitolite3-upstream.zip
Adding upstream version 3.6.12.upstream/3.6.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rwxr-xr-xsrc/triggers/bg17
-rwxr-xr-xsrc/triggers/expand-deny-messages191
-rwxr-xr-xsrc/triggers/partial-copy69
-rwxr-xr-xsrc/triggers/post-compile/create-with-reference39
-rwxr-xr-xsrc/triggers/post-compile/ssh-authkeys142
-rwxr-xr-xsrc/triggers/post-compile/ssh-authkeys-shell-users51
-rwxr-xr-xsrc/triggers/post-compile/ssh-authkeys-split87
-rwxr-xr-xsrc/triggers/post-compile/update-description-file16
-rwxr-xr-xsrc/triggers/post-compile/update-git-configs61
-rwxr-xr-xsrc/triggers/post-compile/update-git-daemon-access-list40
-rwxr-xr-xsrc/triggers/post-compile/update-gitweb-access-list40
-rwxr-xr-xsrc/triggers/post-compile/update-gitweb-daemon-from-options51
-rwxr-xr-xsrc/triggers/renice5
-rwxr-xr-xsrc/triggers/repo-specific-hooks118
-rwxr-xr-xsrc/triggers/set-default-roles20
-rwxr-xr-xsrc/triggers/upstream72
16 files changed, 1019 insertions, 0 deletions
diff --git a/src/triggers/bg b/src/triggers/bg
new file mode 100755
index 0000000..3c66500
--- /dev/null
+++ b/src/triggers/bg
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# quick and dirty program to background any of the triggers programs that are
+# taking too long. To use, just replace a line like
+# 'post-compile/update-gitweb-access-list',
+# with
+# 'bg post-compile/update-gitweb-access-list',
+
+# We dump output to a file in the log directory but please keep in mind this
+# is not a "log" so much as a redirection of the entire output.
+
+echo `date` $GL_TID "$0: $@" >> $GL_LOGFILE.bg
+
+path=${0%/*}
+script=$path/$1; shift
+
+( ( $script "$@" < /dev/null >> $GL_LOGFILE.bg 2>&1 & ) )
diff --git a/src/triggers/expand-deny-messages b/src/triggers/expand-deny-messages
new file mode 100755
index 0000000..107202c
--- /dev/null
+++ b/src/triggers/expand-deny-messages
@@ -0,0 +1,191 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+$|++;
+
+# program name: expand-deny-messages
+
+# DOCUMENTATION IS AT THE BOTTOM OF THIS FILE; PLEASE READ
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+my %attempted_access = (
+ # see triggers.html
+ 'ACCESS_1' => {
+ 'R' => 'Repo read',
+ 'W' => 'Repo write',
+ },
+ 'ACCESS_2' => {
+ 'W' => "Fast forward push",
+ '+' => "Rewind push branch or overwrite tag",
+ 'C' => "Create ref",
+ 'D' => "Delete ref",
+ }
+);
+
+# env var to disable is set?
+exit 0 if $ENV{GL_OPTION_EDM_DISABLE};
+
+# argument 1
+my $a12 = shift; # ACCESS_1 or ACCESS_2
+exit 0 if $a12 !~ /^ACCESS_[12]$/; # shouldn't happen; error in rc file?
+
+# the rest of the arguments
+my ( $repo, $user, $aa, $ref, $msg, $oldsha, $newsha ) = @ARGV;
+
+# we're only interested in deny messages
+exit 0 if $msg !~ /DENIED/;
+
+print STDERR "\nFATAL -- ACCESS DENIED\n";
+
+_info( "Repo", $repo );
+_info( "User", $user );
+_info( "Stage", ( $a12 eq 'ACCESS_1' ? "Before git was called" : "From git's update hook" ) );
+_info( "Ref", _ref($ref) ) if $a12 eq 'ACCESS_2';
+_info( "Operation", _op( $a12, $aa, $oldsha, $newsha ) );
+
+if ( $ref =~ m((^VREF/[^/]+)) ) {
+ my $vref = $1;
+ if ($ref =~ s(^VREF/NAME/)()) {
+ print STDERR "You're apparently not allowed to push '$ref'";
+ } else {
+ my $vref_text = slurp( _which( $vref, 'x' ) );
+ my $etag = '(?:help|explain|explanation)';
+ $vref_text =~ m(^\s*# $etag.start\n(.*)^\s*# $etag.end\n)sm
+ and print STDERR "Explanation for $vref:\n$1";
+ }
+}
+
+print STDERR "\n";
+print STDERR "$ENV{GL_OPTION_EDM_EXTRA_INFO}\n\n" if $ENV{GL_OPTION_EDM_EXTRA_INFO};
+
+# ------------------------------------------------------------------------
+
+sub _ref {
+ my $r = shift;
+ return "VREF '$r'" if $r =~ s(^VREF/)();
+ return "Branch '$r'" if $r =~ s(^refs/heads/)();
+ return "Tag '$r'" if $r =~ s(^refs/tags/)();
+ return "Non-standard ref '$r'";
+}
+
+sub _info {
+ printf STDERR "%-14s %-60s\n", @_;
+}
+
+sub _op {
+ my ( $a12, $aa, $oldsha, $newsha ) = @_;
+
+ # first remove the M part and save the text for later addition if needed
+ my $merge = ( $aa =~ s/M// ? " with merge commit" : "" );
+
+ # next, the attempted access is modified to reflect the actual operation being
+ # attempted. NOTE: this no longer necessarily reflects what the gitolite log
+ # file stores; it's more granular and truly distinguishes a branch create from
+ # an ff push, etc. Could help when user typos a branch name I suppose
+ $aa = 'C' if $oldsha and $oldsha eq '0' x 40;
+ $aa = 'D' if $newsha and $newsha eq '0' x 40;
+
+ # then we map it, add merge text if any
+ my $op = $attempted_access{$a12}{$aa} || "Unknown operation '$aa'";
+ $op .= $merge;
+
+ return $op;
+}
+
+__END__
+
+ENABLING THE FEATURE
+--------------------
+
+To enable this feature, uncomment the line in the rc file if your gitolite was
+installed recently enough. Otherwise you will need to add these lines to the
+end of your rc file, just before the "%RC" block ends:
+
+ ACCESS_1 => [
+ 'expand-deny-messages',
+ ],
+
+ ACCESS_2 => [
+ 'expand-deny-messages',
+ ],
+
+Please don't miss the trailing commas!
+
+DISABLING IT FOR SPECIFIC REPOS
+-------------------------------
+
+Once it is enabled at the rc file level, if you wish to disable it for
+specific repositories just add a line like this to those repos:
+
+ option ENV.EDM_DISABLE = 1
+
+Or you can also disable it for all repos, then enable it for some:
+
+ repo @all
+ option ENV.EDM_DISABLE = 1
+
+ # ... then later ...
+
+ repo foo bar @baz
+ option ENV.EDM_DISABLE = 0
+
+(options.html[1] and pages linked from it will explain how that works).
+
+[1]: http://gitolite.com/gitolite/options.html
+
+SUPPLYING EXTRA INFORMATION
+---------------------------
+
+You can also supply some extra information to be printed, by adding a line
+like this to each repository in the gitolite.conf file:
+
+ option ENV.EDM_EXTRA_INFO = "please contact alice@example.com"
+
+You could of course add it under a "repo @all" section if you like.
+
+SUPPLYING EXTRA INFORMATION FOR VREFs
+-------------------------------------
+
+If you have VREFs that do funky things and you want to **lecture** your users
+when they screw up, add something like the following to your VREF code.
+
+ # help start
+
+ Some help text.
+
+ Some more help text. This can be
+ multi-line.
+
+ (etc etc etc)
+
+ # help end
+
+Then everything between the "# help start" line and the "# help end" line will
+get printed if a users falls afoul of this VREF. If any of the lines shown
+are not valid syntax for your language, figure out some way to put the whole
+thing in a comment block. Here a C example:
+
+ /*
+ # help start
+ line 1
+ line 2
+ ...
+ last line
+ # help end
+ */
+
+Even if your language does not support multi-line comments like C does, there
+may be other ways to specify those lines. Here's an example in shell:
+
+ cat << EOF > /dev/null
+ # help start
+ line 1
+ line 2
+ ...
+ last line
+ # help end
+ EOF
diff --git a/src/triggers/partial-copy b/src/triggers/partial-copy
new file mode 100755
index 0000000..79b4d48
--- /dev/null
+++ b/src/triggers/partial-copy
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+# this is a wee bit expensive in terms of forks etc., compared to doing it in
+# perl, but I wanted to show how *easy* it actually is now. And really,
+# you'll only notice if you access this repo like a hundred times a minute or
+# something so don't sweat it.
+
+# given a repo and a user, check if option('partialCopyOf') is set, and if so,
+# fetch all allowed branches from there.
+
+die() { echo "$@" >&2; exit 1; }
+
+# make sure we're being called from the pre_git trigger
+[ "$1" = "PRE_GIT" ] || die I must be called from PRE_GIT, not "$1"
+shift
+
+repo=$1
+user=$2
+main=`git config --file $GL_REPO_BASE/$repo.git/config --get gitolite.partialCopyOf`;
+[ -z "$main" ] && exit 0
+
+# "we", "our repo" => the partial copy
+# "main", "pco" => the one which we are a "partial copy of"
+
+cd $GL_REPO_BASE/$main.git
+
+for ref in `git for-each-ref refs/heads '--format=%(refname)'`
+do
+ cd $GL_REPO_BASE/$repo.git
+
+ gitolite access -q $repo $user R $ref &&
+ git fetch -f $GL_REPO_BASE/$main.git $ref:$ref
+done
+
+export GL_BYPASS_ACCESS_CHECKS=1
+
+# remove all refs not in main or accessible
+cd $GL_REPO_BASE/$repo.git
+
+for ref in `git for-each-ref refs/heads refs/tags '--format=%(refname)'`
+do
+ cd $GL_REPO_BASE/$main.git
+
+ if git show-ref --verify --quiet $ref &&
+ gitolite access -q $repo $user R $ref ; then
+ # ref is present in main and accessible in repo
+ continue
+ fi
+
+ git push -f $GL_REPO_BASE/$repo.git :$ref || die "FATAL: failed to delete $ref"
+done
+
+# remove all tags no longer reachable
+cd $GL_REPO_BASE/$repo.git
+
+for ref in `git for-each-ref refs/tags '--format=%(refname)'`
+do
+ SHA=`git rev-list -1 $ref`
+ for branch in `git for-each-ref refs/heads '--format=%(refname)'`
+ do
+ if [ "`git merge-base $SHA $branch`" = "$SHA" ]; then
+ # tag is reachable in current branch, continue higher loop
+ continue 2
+ fi
+ done
+ git push -f $GL_REPO_BASE/$repo.git :$ref || die "FATAL: failed to delete $ref"
+done
+
+exit 0
diff --git a/src/triggers/post-compile/create-with-reference b/src/triggers/post-compile/create-with-reference
new file mode 100755
index 0000000..f525082
--- /dev/null
+++ b/src/triggers/post-compile/create-with-reference
@@ -0,0 +1,39 @@
+#!/usr/bin/perl
+
+# Set alternates if option reference.repo is set
+# ----------------------------------------------------------------------
+
+use FindBin;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+my $RB = $rc{GL_REPO_BASE};
+
+if ( @ARGV and $ARGV[0] eq 'POST_CREATE' ) {
+ my $repo = $ARGV[1];
+ create_alternates($repo);
+
+ exit 0;
+}
+
+# not interested in any other triggers
+exit 0;
+
+sub create_alternates {
+ my $pr = shift;
+
+ my $refrepos = git_config( $pr, "^gitolite-options\\.reference\\.repo.*" );
+ my %list = map { $_ => 1 } map { split } values %$refrepos;
+ my @alts = keys %list;
+ if ( @alts ) {
+ my $altlist = join "\n", map { "$RB/$_.git/objects" } @alts;
+ _print( "$RB/$pr.git/objects/info/alternates", "$altlist\n" );
+
+ }
+}
diff --git a/src/triggers/post-compile/ssh-authkeys b/src/triggers/post-compile/ssh-authkeys
new file mode 100755
index 0000000..cd59aec
--- /dev/null
+++ b/src/triggers/post-compile/ssh-authkeys
@@ -0,0 +1,142 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Getopt::Long;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+$|++;
+
+# best called via 'gitolite trigger POST_COMPILE'; other modes at your own
+# risk, especially if the rc file specifies arguments for it. (That is also
+# why it doesn't respond to "-h" like most gitolite commands do).
+
+# option procesing
+# ----------------------------------------------------------------------
+
+# currently has one option:
+# -kfn, --key-file-name adds the keyfilename as a second argument
+
+my $kfn = '';
+GetOptions( 'key-file-name|kfn' => \$kfn, );
+
+tsh_try("sestatus");
+my $selinux = ( tsh_text() =~ /enforcing/ );
+
+my $ab = $rc{GL_ADMIN_BASE};
+trace( 1, "'keydir' not found in '$ab'; exiting" ), exit if not -d "$ab/keydir";
+my $akdir = "$ENV{HOME}/.ssh";
+my $akfile = "$ENV{HOME}/.ssh/authorized_keys";
+my $glshell = $rc{GL_BINDIR} . "/gitolite-shell";
+my $auth_options = auth_options();
+
+sanity();
+
+# ----------------------------------------------------------------------
+
+_chdir($ab);
+
+# old data
+my $old_ak = slurp($akfile);
+my @non_gl = grep { not /^# gito.*start/ .. /^# gito.*end/ } slurp($akfile);
+chomp(@non_gl);
+my %seen = map { $_ => 'a non-gitolite key' } ( fp(@non_gl) );
+
+# pubkey files
+chomp( my @pubkeys = `find keydir/ -type f -name "*.pub" | sort` );
+my @gl_keys = ();
+for my $f (@pubkeys) {
+ my $fp = fp($f);
+ if ( $seen{$fp} ) {
+ _warn "$f duplicates $seen{$fp}, sshd will ignore it";
+ } else {
+ $seen{$fp} = $f;
+ }
+ push @gl_keys, grep { /./ } optionise($f);
+}
+
+# dump it out
+my $out = join( "\n", @non_gl, "# gitolite start", @gl_keys, "# gitolite end" ) . "\n";
+
+my $ak = slurp($akfile);
+_die "'$akfile' changed between start and end of this program!" if $ak ne $old_ak;
+_print( $akfile, $out );
+
+_warn "you have no keys left; I hope you intended to do that!" unless @gl_keys;
+
+# ----------------------------------------------------------------------
+
+sub sanity {
+ _die "'$glshell' not found; this should NOT happen..." if not -f $glshell;
+ _die "'$glshell' found but not readable; this should NOT happen..." if not -r $glshell;
+ _die "'$glshell' found but not executable; this should NOT happen..." if not -x $glshell;
+
+ my $n = " (this is normal on a brand new install)";
+ _warn "$akdir missing; creating a new one\n$n" if not -d $akdir;
+ _warn "$akfile missing; creating a new one\n$n" if not -f $akfile;
+
+ _mkdir( $akdir, 0700 ) if not -d $akdir;
+ if ( not -f $akfile ) {
+ _print( $akfile, "" );
+ chmod 0600, $akfile;
+ }
+}
+
+sub auth_options {
+ my $auth_options = $rc{AUTH_OPTIONS};
+ $auth_options ||= "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty";
+
+ return $auth_options;
+}
+
+sub fp {
+ # input: see below
+ # output: a (list of) FPs
+ my $in = shift || '';
+ if ( $in =~ /\.pub$/ ) {
+ # single pubkey file
+ _die "bad pubkey file '$in'" unless $in =~ $REPONAME_PATT;
+ return fp_file($in);
+ } elsif ( -f $in ) {
+ # an authkeys file
+ return map { fp_line($_) } grep { !/^#/ and /\S/ } slurp($in);
+ } else {
+ # one or more actual keys
+ return map { fp_line($_) } grep { !/^#/ and /\S/ } ( $in, @_ );
+ }
+}
+
+sub fp_file {
+ return $selinux++ if $selinux; # return a unique "fingerprint" to prevent noise
+ my $f = shift;
+ my ($fp, $output) = ssh_fingerprint_file($f);
+ _die "fingerprinting failed for '$f': $output" unless $fp;
+ return $fp;
+}
+
+sub fp_line {
+ my $line = shift;
+ my ($fp, $output) = ssh_fingerprint_line($line);
+ _die "fingerprinting failed for '$line': $output" unless $fp;
+ return $fp;
+}
+
+sub optionise {
+ my $f = shift;
+
+ my $user = $f;
+ $user =~ s(.*/)(); # foo/bar/baz.pub -> baz.pub
+ $user =~ s/(\@[^.]+)?\.pub$//; # baz.pub, baz@home.pub -> baz
+
+ my @line = slurp($f);
+ if ( @line != 1 ) {
+ _warn "$f does not contain exactly 1 line; ignoring";
+ return '';
+ }
+ chomp(@line);
+ return "command=\"$glshell $user" . ( $kfn ? " $f" : "" ) . "\",$auth_options $line[0]";
+}
+
diff --git a/src/triggers/post-compile/ssh-authkeys-shell-users b/src/triggers/post-compile/ssh-authkeys-shell-users
new file mode 100755
index 0000000..2dd6643
--- /dev/null
+++ b/src/triggers/post-compile/ssh-authkeys-shell-users
@@ -0,0 +1,51 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+$|++;
+
+my $akfile = "$ENV{HOME}/.ssh/authorized_keys";
+
+# ----------------------------------------------------------------------
+
+my $aktext = slurp($akfile);
+
+for my $su ( shell_users() ) {
+ $aktext =~ s(/gitolite-shell $su([" ].*?),no-pty )(/gitolite-shell -s $su$1 )g;
+}
+
+_print( $akfile, $aktext );
+
+# two methods to specify list of shell-capable users. (1) list of usernames
+# as arguments to 'Shell' in rc file, (2) list of usernames in a plain text
+# file whose name is the first argument to 'Shell' in the rc file. Or both!
+sub shell_users {
+ my ($sufile, @ret);
+
+ # backward compat for 3.6 and below. This code will be removed in 3.7.
+ # Also, the variable is ignored if you end up using the new variant (i.e.,
+ # put a file name on the 'Shell' line itself).
+ $sufile = $rc{SHELL_USERS_LIST} if $rc{SHELL_USERS_LIST} and -r $rc{SHELL_USERS_LIST};
+
+ $sufile = shift @ARGV if @ARGV and -r $ARGV[0];
+
+ if ($sufile) {
+ @ret = grep { not /^#/ } slurp($sufile);
+ chomp(@ret);
+ }
+
+ for my $u (@ARGV) {
+ # arguments placed in the rc file appear before the trigger name
+ last if $u eq 'POST_COMPILE';
+
+ push @ret, $u;
+ # no sanity checking, since the rc file can only be created by someone
+ # who already has shell access
+ }
+ _die "'Shell': enabled but no usernames supplied" unless @ret;
+ return @ret;
+}
diff --git a/src/triggers/post-compile/ssh-authkeys-split b/src/triggers/post-compile/ssh-authkeys-split
new file mode 100755
index 0000000..031bd07
--- /dev/null
+++ b/src/triggers/post-compile/ssh-authkeys-split
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+# split multi-key files into separate keys like ssh-authkeys likes
+
+# WHY
+# ---
+#
+# Yeah I wonder that too, when it's so much more maintainable to keep the damn
+# keys as sitaram@home.pub and sitaram@work.pub or such. But there's no
+# accounting for tastes, and some old fogies apparently want to put all of a
+# user's keys into a single ".pub" file.
+
+# WARNINGS AND CAVEATS
+# --------------------
+#
+# - assumes no "@" sign in basenames of any multi-key files (single line file
+# may still have them)
+
+# - assumes you don't have a subdir in keydir called "__split_keys__"
+
+# SUPPORT
+# -------
+#
+# NONE.
+
+# USAGE
+# -----
+#
+# to enable, uncomment the 'ssh-authkeys-split' line in the ENABLE list in the
+# rc file.
+
+cd $GL_ADMIN_BASE/keydir
+
+rm -rf __split_keys__
+mkdir __split_keys__
+export SKD=$PWD/__split_keys__
+
+# if we're coming from a gitolite-admin push, delete all *.multi, and rename
+# all multi-line *.pub to *.multi
+if [ "$GL_REPO" = "gitolite-admin" ] || [ "$GL_BYPASS_ACCESS_CHECKS" = "1" ]
+then
+ find . -type f -name "*.multi" | while read k
+ do
+ rm -f "$k"
+ done
+ find . -type f -name "*.pub" | while read k
+ do
+ # is this a multi-key?
+ lines=`wc -l < $k`
+ case $lines in
+ (0|1) continue
+ esac
+
+ base=`basename $k .pub`
+ mv $k $base.multi
+ done
+fi
+
+# now process *.multi
+find . -type f -name "*.multi" | while read k
+do
+ # do we need to split?
+ lines=`wc -l < $k`
+ case $lines in
+ (0|1) continue
+ esac
+
+ base=`basename $k .multi`
+ # sanity check
+ echo $base | grep '@' >/dev/null && continue
+
+ # ok do it
+ seq=0
+ while read line
+ do
+ (( seq++ ))
+ [ -z "$line" ] && continue
+ f=$SKD/$base@$seq.pub
+ echo "$line" > $f
+ # similar sanity check as main ssh-authkeys script
+ if ! ssh-keygen -l -f $f >/dev/null
+ then
+ echo 1>&2 "ssh-authkeys-split: bad line $seq in keydir/$k"
+ rm -f $f
+ fi
+ done < $k
+done
diff --git a/src/triggers/post-compile/update-description-file b/src/triggers/post-compile/update-description-file
new file mode 100755
index 0000000..e5b7c6a
--- /dev/null
+++ b/src/triggers/post-compile/update-description-file
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+# For normal (not "wild") repos, gitolite v3 sets 'gitweb.description' instead
+# of putting the text in the "description" file. This is easier because it
+# just goes with the flow of setting config variables; nothing special needs
+# to be done for the description.
+
+# But this only works for gitweb, not for cgit. Cgit users must uncomment the
+# 'cgit' line in the ENABLE list in the rc file (which has the effect of
+# adding this program to the POST_COMPILE trigger list).
+
+cd $GL_REPO_BASE
+gitolite list-phy-repos | gitolite git-config % gitweb.description | perl -I"$GL_LIBDIR" -MGitolite::Easy -lne '
+ my @F = split /\t/,$_,3;
+ textfile( file => "description", repo => $F[0], text => $F[2] );
+ '
diff --git a/src/triggers/post-compile/update-git-configs b/src/triggers/post-compile/update-git-configs
new file mode 100755
index 0000000..6eb2f46
--- /dev/null
+++ b/src/triggers/post-compile/update-git-configs
@@ -0,0 +1,61 @@
+#!/usr/bin/perl
+
+# update git-config entries in each repo
+# ----------------------------------------------------------------------
+
+use FindBin;
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+use Gitolite::Conf::Load;
+
+use strict;
+use warnings;
+
+my $RB = $rc{GL_REPO_BASE};
+_chdir($RB);
+
+# ----------------------------------------------------------------------
+# if called from POST_CREATE, we have only a single repo to worry about
+if ( @ARGV and $ARGV[0] eq 'POST_CREATE' ) {
+ my $repo = $ARGV[1];
+ fixup_config($repo);
+
+ exit 0;
+}
+
+# ----------------------------------------------------------------------
+# else it's all repos (i.e., called from POST_COMPILE)
+
+my $lpr = list_phy_repos();
+
+for my $pr (@$lpr) {
+ fixup_config($pr);
+}
+
+sub fixup_config {
+ my $pr = shift;
+ my $creator = creator($pr);
+
+ my $gc = git_config( $pr, '.', 1 );
+ my $ac = `git config --file $RB/$pr.git/config -l`;
+ while ( my ( $key, $value ) = each( %{$gc} ) ) {
+ next if $key =~ /^gitolite-options\./;
+ $value =~ s/(@\w+)/expand_group($1)/ge if $rc{EXPAND_GROUPS_IN_CONFIG};
+ my $lkey = lc $key;
+ next if $ac =~ /^\Q$lkey\E=\Q$value\E$/m;
+ if ( $value ne "" ) {
+ system( "git", "config", "--file", "$RB/$pr.git/config", $key, $value );
+ } elsif ( $ac =~ /^\Q$lkey\E=/m ) {
+ system( "git", "config", "--file", "$RB/$pr.git/config", "--unset-all", $key );
+ }
+ }
+}
+
+sub expand_group {
+ my $g = shift;
+ my @m = @{ Gitolite::Conf::Load::list_members($1) };
+ return join(" ", @m) if @m;
+ return $g;
+}
diff --git a/src/triggers/post-compile/update-git-daemon-access-list b/src/triggers/post-compile/update-git-daemon-access-list
new file mode 100755
index 0000000..ade97a8
--- /dev/null
+++ b/src/triggers/post-compile/update-git-daemon-access-list
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+
+# update git-daemon-export-ok files in each repo
+# ----------------------------------------------------------------------
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Easy;
+use Gitolite::Common;
+
+use strict;
+use warnings;
+
+my $EO = "git-daemon-export-ok";
+my $RB = $rc{GL_REPO_BASE};
+
+my $cmd = "gitolite list-phy-repos";
+if ( @ARGV and $ARGV[0] eq 'POST_CREATE' ) {
+ # only one repo to do
+ $cmd = "echo $ARGV[1]";
+}
+
+for my $d (`$cmd | gitolite access % daemon R any`) {
+ my @F = split "\t", $d;
+ if ($F[2] =~ /DENIED/) {
+ unlink "$RB/$F[0].git/$EO";
+ } elsif (! -f "$RB/$F[0].git/$EO") {
+ textfile( file => $EO, repo => $F[0], text => "" );
+ }
+}
+
+# As a quick recap, the gitolite output looks somewhat like this:
+
+# bar^Idaemon^IR any bar daemon DENIED by fallthru$
+# foo^Idaemon^Irefs/.*$
+# fubar^Idaemon^Irefs/.*$
+# gitolite-admin^Idaemon^IR any gitolite-admin daemon DENIED by fallthru$
+# testing^Idaemon^Irefs/.*$
+
+# where I've typed "^I" to denote a tab.
diff --git a/src/triggers/post-compile/update-gitweb-access-list b/src/triggers/post-compile/update-gitweb-access-list
new file mode 100755
index 0000000..4085d59
--- /dev/null
+++ b/src/triggers/post-compile/update-gitweb-access-list
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+# this is literally the simplest gitweb update possible. You are free to add
+# whatever you want and contribute it back, as long as it is upward
+# compatible.
+
+# ----------------------------------------------------------------------
+# delete the 'description' file that 'git init' created if this is run from
+# the post-create trigger. However, note that POST_CREATE is also called from
+# perms (since POST_CREATE doubles as eqvt of POST_COMPILE to propagate ad hoc
+# permissions changes for wild repos) and then you should not delete it.
+[ "$1" = "POST_CREATE" ] && [ "$4" != "perms" ] && rm -f $GL_REPO_BASE/$2.git/description 2>/dev/null
+
+plf=`gitolite query-rc GITWEB_PROJECTS_LIST`
+[ -z "$plf" ] && plf=$HOME/projects.list
+# since mktemp does not honor umask, we just use it to generate a temp
+# filename (note: 'mktemp -u' on some systems, this gets close enough)
+tmpfile=`mktemp $plf.tmp_XXXXXXXX`
+rm -f $tmpfile;
+
+if [ "$1" = "POST_CREATE" ] && [ -n "$2" ]
+then
+ # just one to be done
+ repo="$2"
+ grep -v "^$repo.git$" $plf > $tmpfile
+ if gitolite access -q $repo gitweb R any || gitolite git-config -q -r $repo gitweb\\.
+ then
+ echo "$repo.git" >> $tmpfile
+ fi
+else
+ # all of them
+ (
+ gitolite list-phy-repos | gitolite access % gitweb R any | grep -v DENIED
+ gitolite list-phy-repos | gitolite git-config -r % gitweb\\.
+ ) |
+ cut -f1 | sort -u | sed -e 's/$/.git/' > $tmpfile
+fi
+
+[ -f $plf ] && perl -e "chmod ( ( (stat('$plf'))[2] & 07777 ), '$tmpfile')"
+mv $tmpfile $plf
diff --git a/src/triggers/post-compile/update-gitweb-daemon-from-options b/src/triggers/post-compile/update-gitweb-daemon-from-options
new file mode 100755
index 0000000..1f5fd26
--- /dev/null
+++ b/src/triggers/post-compile/update-gitweb-daemon-from-options
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+# TODO: look at the commit in which *this* line was added, and see the changes
+# to the other scripts. We need to make those changes here also, but I'm too
+# lazy right now. Plus I'm not even sure if anyone is using this!
+
+# Update git-daemon and gitweb access using 'option' lines instead of special
+# usernames.
+
+# To use:
+
+# * enable this combined updater in the rc file by removing the other two
+# update-*-access-list entries and inserting this one instead. (This would
+# be in the POST_CREATE and POST_COMPILE lists).
+
+# * the add option lines in the conf file, like this:
+#
+# repo foo @bar
+# option daemon = 1
+# option gitweb = 1
+
+# Note: don't forget that gitweb can also be enabled by actual config
+# variables (gitweb.owner, gitweb.description, gitweb.category)
+
+# This is useful for people who don't like '@all' to be literally *all* users,
+# including gitweb and daemon, and can't/won't use deny-rules properly.
+
+# first do the gitweb stuff
+
+plf=`gitolite query-rc GITWEB_PROJECTS_LIST`
+[ -z "$plf" ] && plf=$HOME/projects.list
+
+(
+ gitolite list-phy-repos | gitolite git-config % gitolite-options.gitweb
+ gitolite list-phy-repos | gitolite git-config -r % gitweb\\.
+) |
+ cut -f1 | sort -u | sed -e 's/$/.git/' > $plf
+
+# now deal with git-daemon
+
+EO=git-daemon-export-ok
+RB=`gitolite query-rc GL_REPO_BASE`
+export EO RB
+
+export tmp=$(mktemp -d)
+trap "rm -rf $tmp" 0
+
+gitolite list-phy-repos | sort | tee $tmp/all | gitolite git-config % gitolite-options.daemon | cut -f1 > $tmp/daemon
+
+comm -23 $tmp/all $tmp/daemon | perl -lne 'unlink "$ENV{RB}/$_.git/$ENV{EO}"'
+cat $tmp/daemon | perl -I"$GL_LIBDIR" -MGitolite::Easy -lne 'textfile( file => $ENV{EO}, repo => $_, text => "");'
diff --git a/src/triggers/renice b/src/triggers/renice
new file mode 100755
index 0000000..ba0b726
--- /dev/null
+++ b/src/triggers/renice
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+n=$1
+[ "$n" = "PRE_GIT" ] && n=10
+renice -n $n $GL_TID >/dev/null
diff --git a/src/triggers/repo-specific-hooks b/src/triggers/repo-specific-hooks
new file mode 100755
index 0000000..4044cc9
--- /dev/null
+++ b/src/triggers/repo-specific-hooks
@@ -0,0 +1,118 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# setup repo-specific hooks
+
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Rc;
+use Gitolite::Common;
+
+_die "repo-specific-hooks: LOCAL_CODE not defined in rc" unless $rc{LOCAL_CODE};
+_die "repo-specific-hooks: '$rc{LOCAL_CODE}/hooks/repo-specific' does not exist or is not a directory" unless -d "$rc{LOCAL_CODE}/hooks/repo-specific";
+
+_chdir( $ENV{GL_REPO_BASE} );
+
+if ($ARGV[0] eq 'POST_CREATE') {
+ # just the repo given in arg-2
+ @ARGV = ("gitolite git-config -ev -r $ARGV[1] gitolite-options\\.hook\\. |");
+} else {
+ # POST_COMPILE, all repos
+ @ARGV = ("gitolite list-phy-repos | gitolite git-config -ev -r % gitolite-options\\.hook\\. |");
+}
+
+my $driver = $rc{MULTI_HOOK_DRIVER} || "$rc{LOCAL_CODE}/hooks/multi-hook-driver";
+# Hook Driver
+{
+ local $/ = undef;
+ my $hook_text = <DATA>;
+ _print( $driver, $hook_text );
+ chmod 0755, $driver;
+}
+
+my %repo_hooks;
+while (<>) {
+ chomp;
+ my ( $repo, $hook, $codes ) = split /\t/, $_;
+ $codes ||= '';
+
+ # get the hook name
+ $hook =~ s/^gitolite-options\.hook\.//;
+ $hook =~ s/\..*//;
+
+ my @codes = split /\s+/, $codes;
+
+ # bail on disallowed hook types (but warn only if @codes is non-empty)
+ if ( $repo eq 'gitolite-admin' and $hook eq 'post-update' ) {
+ _warn "repo-specific-hooks: ignoring attempts to set post-update hook for the admin repo" if @codes;
+ next;
+ }
+ unless ( $hook =~ /^(pre-receive|post-receive|post-update|pre-auto-gc)$/ ) {
+ if (@codes) {
+ _warn "repo-specific-hooks: '$hook' is not allowed, ignoring";
+ _warn " (only pre-receive, post-receive, post-update, and pre-auto-gc are allowed)";
+ }
+ next;
+ }
+
+ push @{ $repo_hooks{$repo}{$hook} }, @codes;
+}
+
+for my $repo (keys %repo_hooks) {
+ for my $hook (keys %{ $repo_hooks{$repo} }) {
+ my @codes = @{ $repo_hooks{$repo}{$hook} };
+
+ my $dst = "$repo.git/hooks/$hook";
+ unlink( glob("$dst.*") );
+
+ my $counter = "h00";
+ foreach my $code (@codes) {
+ if ( $code =~ m(^/|\.\.) ) {
+ _warn "repo-specific-hooks: double dot or leading slash not allowed in '$code'";
+ next;
+ }
+
+ my $src = $rc{LOCAL_CODE} . "/hooks/repo-specific/$code";
+
+ # if $code has slashes in it, flatten it for use in $dst, to avoid
+ # having to re-create those intermediate sub-directories
+ $code =~ s(/)(_)g;
+ my $dst = "$repo.git/hooks/$hook.$counter-$code";
+
+ unless ( -x $src ) {
+ _warn "repo-specific-hooks: '$src' doesn't exist or is not executable";
+ next;
+ }
+ unlink $dst;
+ symlink $src, $dst or _warn "could not symlink '$src' to '$dst'";
+ $counter++;
+
+ # no sanity checks for multiple overwrites of the same hook
+ }
+
+ unlink $dst;
+ symlink $driver, $dst or die "could not symlink '$driver' to '$dst'";
+ }
+}
+
+__DATA__
+#!/bin/sh
+
+# Determine what input the hook needs
+# post-update takes args, pre/post-receive take stdin
+type=args
+stdin=''
+[ $0 != hooks/post-update ] && {
+ type=stdin
+ stdin=`cat`
+}
+
+for h in $0.*; do
+ [ -x $h ] || continue
+ if [ $type = args ]
+ then
+ $h $@ || { [ $0 = hooks/pre-receive ] && exit 1; }
+ else
+ echo "$stdin" | $h || { [ $0 = hooks/pre-receive ] && exit 1; }
+ fi
+done
diff --git a/src/triggers/set-default-roles b/src/triggers/set-default-roles
new file mode 100755
index 0000000..dbbcc92
--- /dev/null
+++ b/src/triggers/set-default-roles
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+# POST_CREATE trigger to set up default set of perms for a new wild repo
+
+# ----------------------------------------------------------------------
+# skip if arg-1 is POST_CREATE and no arg-3 (user name) exists (i.e., it's not
+# a wild repo)
+[ "$1" = "POST_CREATE" ] && [ -z "$3" ] && exit 0;
+[ "$4" = "R" ] || [ "$4" = "W" ] || [ "$4" = "perms-c" ] || [ "$4" = "create" ] || [ "$4" = "fork" ] || exit 0
+
+die() { echo "$@" >&2; exit 1; }
+
+cd $GL_REPO_BASE/$2.git || die "could not cd to $GL_REPO_BASE/$2.git"
+gitolite git-config -r $2 gitolite-options.default.roles | sort | cut -f3 |
+ perl -pe 's/(\s)CREATOR(\s|$)/$1$ENV{GL_USER}$2/' > gl-perms
+
+# cache control, if rc says caching is on
+gitolite query-rc -q CACHE && perl -I$GL_LIBDIR -MGitolite::Cache -e "cache_control('flush', '$2')";
+
+exit 0
diff --git a/src/triggers/upstream b/src/triggers/upstream
new file mode 100755
index 0000000..611e11e
--- /dev/null
+++ b/src/triggers/upstream
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+# manage local, gitolite-controlled, copies of read-only upstream repos.
+
+repo=$2
+
+url=$(gitolite git-config $repo gitolite-options.upstream.url)
+[ -z "$url" ] && exit 0 # exit if no url was specified
+
+cd $GL_REPO_BASE/$repo.git || exit 1
+
+[ "$1" != "fetch" ] && {
+ nice=$(gitolite git-config $repo gitolite-options.upstream.nice)
+ [ -n "$nice" ] && find FETCH_HEAD -mmin -$nice 2>/dev/null | grep . >/dev/null && exit 0
+}
+
+git fetch -q "$url" '+refs/*:refs/*'
+
+# ----------------------------------------------------------------------
+
+# FEATURES:
+# * invokes upstream fetch on each local fetch
+# (unless the optional 'nice' setting is enabled)
+# * can force a fetch (ignoring 'nice' value) from server CLI
+
+# INSTRUCTIONS:
+#
+# * uncomment 'upstream' in the ENABLE list in the rc file.
+# * add option lines to conf file. For example:
+#
+# repo git
+# R = @all
+# RW+ my-company/ = @developers
+#
+# option upstream.url = https://git.kernel.org/pub/scm/git/git.git
+# option upstream.nice = 120
+#
+# * to force a fetch on the server shell (or via cron), run this command:
+# gitolite ../triggers/upstream fetch reponame
+
+# ADDITIONAL NOTES:
+# * restrict local pushes to a namespace that the upstream won't use
+# (otherwise the next fetch will wipe them out)
+# * if the upstream URL changes, just change the conf and push admin repo
+# * the 'nice' setting is in minutes and is optional; it is the minimum
+# elapsed time between 2 upstream fetches.
+
+# USAGE EXAMPLE:
+#
+# Let's say you want to keep a read-only local mirror of all your github repos
+# on your local gitolite installation. Assuming your github usernames are the
+# same as your local usernames, and you have updated GIT_CONFIG_KEYS in the rc
+# file to allow 'config' lines, you can do this:
+#
+# repo github/CREATOR/..*
+# C = @all
+# R = @all
+# option upstream.url = https://github.com/%GL_REPO.git
+# option upstream.nice = 120
+# config url.https://github.com/.insteadOf = https://github.com/github/
+#
+# Now you can make local, read-only, clones of all your github repos with
+#
+# git ls-remote gitolite:github/sitaramc/gitolite
+# git ls-remote gitolite:github/sitaramc/hap
+# (etc)
+#
+# and if milki were also a user on this gitolite instance, then
+#
+# git ls-remote gitolite:github/milki/xclip
+# git ls-remote gitolite:github/milki/ircblogger
+# (etc)