diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:17:27 +0000 |
commit | aae1a14ea756102251351d96e2567b4986d30e2b (patch) | |
tree | a1af617672e26aee4c1031a3aa83e8ff08f6a0a5 /src/triggers | |
parent | Initial commit. (diff) | |
download | gitolite3-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-x | src/triggers/bg | 17 | ||||
-rwxr-xr-x | src/triggers/expand-deny-messages | 191 | ||||
-rwxr-xr-x | src/triggers/partial-copy | 69 | ||||
-rwxr-xr-x | src/triggers/post-compile/create-with-reference | 39 | ||||
-rwxr-xr-x | src/triggers/post-compile/ssh-authkeys | 142 | ||||
-rwxr-xr-x | src/triggers/post-compile/ssh-authkeys-shell-users | 51 | ||||
-rwxr-xr-x | src/triggers/post-compile/ssh-authkeys-split | 87 | ||||
-rwxr-xr-x | src/triggers/post-compile/update-description-file | 16 | ||||
-rwxr-xr-x | src/triggers/post-compile/update-git-configs | 61 | ||||
-rwxr-xr-x | src/triggers/post-compile/update-git-daemon-access-list | 40 | ||||
-rwxr-xr-x | src/triggers/post-compile/update-gitweb-access-list | 40 | ||||
-rwxr-xr-x | src/triggers/post-compile/update-gitweb-daemon-from-options | 51 | ||||
-rwxr-xr-x | src/triggers/renice | 5 | ||||
-rwxr-xr-x | src/triggers/repo-specific-hooks | 118 | ||||
-rwxr-xr-x | src/triggers/set-default-roles | 20 | ||||
-rwxr-xr-x | src/triggers/upstream | 72 |
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) |