33d6856f4b
The downside is that the repo config does need to be edited and new style line(s) added.
120 lines
4.5 KiB
Perl
Executable file
120 lines
4.5 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);
|
|
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};
|
|
our $gl_user = $ENV{GL_USER};
|
|
|
|
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;
|
|
# 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;
|