the changes to cp/scp are because without "-p" they dont carry perms across to existing files. So if you forgot to chmod +x your custom hook and ran easy install, then after that you have to go to the server side to fix the perms...
121 lines
4.7 KiB
Perl
Executable file
121 lines
4.7 KiB
Perl
Executable file
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
# === update ===
|
|
# this is gitolite's update hook
|
|
|
|
# part of the gitolite (GL) suite
|
|
|
|
# how run: via git, being copied as .git/hooks/update in every repo
|
|
# when: every push
|
|
# input:
|
|
# - see man githooks for STDIN
|
|
# - uses the compiled config file to get permissions info
|
|
# output: based on permissions etc., exit 0 or 1
|
|
# security:
|
|
# - none
|
|
|
|
# robustness:
|
|
|
|
# other notes:
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# common definitions
|
|
# ----------------------------------------------------------------------------
|
|
|
|
our ($GL_CONF_COMPILED, $PERSONAL);
|
|
our %repos;
|
|
|
|
# we should already have the GL_RC env var set when we enter this hook
|
|
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
|
|
# then "do" the compiled config file, whose name we now know. Before doing
|
|
# that we setup the creater etc from environment variables so that the parse
|
|
# interpolates them. We've minimised the duplication but this *does*
|
|
# duplicate a bit of parse_acl from gitolite.pm; we don't want to include that
|
|
# file here just for that little bit
|
|
{
|
|
our $creater = $ENV{GL_CREATER};
|
|
our $readers = $ENV{GL_READERS};
|
|
our $writers = $ENV{GL_WRITERS};
|
|
|
|
die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED;
|
|
|
|
$repos{$ENV{GL_REPO}} = $repos{$ENV{GL_REPOPATT}} if ( $ENV{GL_REPOPATT} );
|
|
}
|
|
my $reported_repo = $ENV{GL_REPO} . ( $ENV{GL_REPOPATT} ? " ($ENV{GL_REPOPATT})" : "" );
|
|
|
|
# we've started to need some common subs in what used to be a small, cute,
|
|
# little script that barely spanned a few lines :(
|
|
require "$ENV{GL_BINDIR}/gitolite.pm";
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# start...
|
|
# ----------------------------------------------------------------------------
|
|
|
|
my @saved_ARGV = @ARGV; # for chaining to another update hook at the end
|
|
|
|
my $ref = shift;
|
|
my $oldsha = shift;
|
|
my $newsha = shift;
|
|
my $merge_base = '0' x 40;
|
|
# compute a merge-base if both SHAs are non-0, else leave it as '0'x40
|
|
# (i.e., for branch create or delete, merge_base == '0'x40)
|
|
chomp($merge_base = `git merge-base $oldsha $newsha`)
|
|
unless $oldsha eq '0' x 40
|
|
or $newsha eq '0' x 40;
|
|
|
|
# what are you trying to do? (is it 'W' or '+'?)
|
|
my $perm = 'W';
|
|
# rewriting a tag is considered a rewind, in terms of permissions
|
|
$perm = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40);
|
|
# non-ff push to ref
|
|
# notice that ref delete looks like a rewind, as it should
|
|
$perm = '+' if $oldsha ne $merge_base;
|
|
|
|
my @allowed_refs;
|
|
# personal stuff -- right at the start in the new regime, I guess!
|
|
push @allowed_refs, { "$PERSONAL/$ENV{GL_USER}/" => "RW+" } if $PERSONAL;
|
|
# we want specific perms to override @all, so they come first
|
|
push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$ENV{GL_USER}} || [] };
|
|
push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{'@all'} || [] };
|
|
|
|
# prepare the list of refs to be checked
|
|
|
|
# previously, we just checked $ref -- the ref being updated, which is passed
|
|
# to us by git (see man githooks). Now we also have to treat each NAME being
|
|
# updated as a potential "ref" and check that, if NAME-based restrictions have
|
|
# been specified
|
|
|
|
my @refs = ($ref); # the first ref to check is the real one
|
|
if (exists $repos{$ENV{GL_REPO}}{NAME_LIMITS}) {
|
|
# this is special to git -- the hash of an empty tree
|
|
my $empty='4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
|
# well they're not really "trees" but $empty is indeed the empty tree so
|
|
# we can just pretend $oldsha/$newsha are also trees, and anyway 'git
|
|
# diff' only wants trees
|
|
my $oldtree = $oldsha eq '0' x 40 ? $empty : $oldsha;
|
|
my $newtree = $newsha eq '0' x 40 ? $empty : $newsha;
|
|
push @refs, map { chomp; s/^/NAME\//; $_; } `git diff --name-only $oldtree $newtree`;
|
|
}
|
|
|
|
# and in this version, we have many "refs" to check. The one we print in the
|
|
# log is the *first* one (which is a *real* ref, like refs/heads/master),
|
|
# while all the rest (if they exist) are like NAME/something. So we do the
|
|
# first one separately to capture it, then run the rest (if any)
|
|
my $log_refex = check_ref(\@allowed_refs, $ENV{GL_REPO}, (shift @refs), $perm);
|
|
&check_ref (\@allowed_refs, $ENV{GL_REPO}, $_ , $perm) for @refs;
|
|
|
|
# if we returned at all, all the checks succeeded, so we log the action and exit 0
|
|
|
|
&log_it("$ENV{GL_TS} $perm\t" .
|
|
substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) .
|
|
"\t$reported_repo\t$ref\t$ENV{GL_USER}\t$log_refex\n");
|
|
|
|
# now chain to the local admin defined update hook, if present
|
|
exec "./hooks/update.secondary", @saved_ARGV
|
|
if -f "hooks/update.secondary" or -l "hooks/update.secondary";
|
|
|
|
exit 0;
|