cf0e568c89
If you have many thousands of repos and users, neatly organised into groups, etc., the normal gitolite fails. (It actually runs out of memory very fast while doing the "compile" when you push the config, due to the number of combinations of repo/user being stored in the hash!) This commit series will stop doing that if you set $GL_BIG_CONFIG = 1 in the rc file. Some notes: - deny rules will still work but somewhat differently -- now they must be placed all together in one place to work like before. Ask me for details if you need to know before I get done with the docs - I've tested most of the important features, but not every single nuance - the update hook may be a tad less efficient now; we can try and tweak it later if needed but it shouldn't really hurt anything significantly even now - docs have not been written yet
116 lines
4.5 KiB
Perl
Executable file
116 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, $UPDATE_CHAINS_TO);
|
|
our %repos;
|
|
|
|
# people with shell access should be allowed to bypass the update hook, simply
|
|
# by setting an env var that the ssh "front door" will never set
|
|
exit 0 if exists $ENV{GL_BYPASS_UPDATE_HOOK};
|
|
|
|
# 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};
|
|
|
|
require "$ENV{GL_BINDIR}/gitolite.pm";
|
|
|
|
my ($perm, $creator, $wild) = &repo_rights($ENV{GL_REPO});
|
|
my $reported_repo = $ENV{GL_REPO} . ( $ENV{GL_REPOPATT} ? " ($ENV{GL_REPOPATT})" : "" );
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 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;
|
|
|
|
# att_acc == attempted access -- what are you trying to do? (is it 'W' or '+'?)
|
|
my $att_acc = 'W';
|
|
# rewriting a tag is considered a rewind, in terms of permissions
|
|
$att_acc = '+' 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
|
|
$att_acc = '+' if $oldsha ne $merge_base;
|
|
|
|
# were any 'D' perms specified? If they were, it means we have to separate
|
|
# deletes from rewinds, so if the new sha is all 0's, change the '+' to a 'D'
|
|
$att_acc = 'D' if ( $repos{$ENV{GL_REPO}}{DELETE_IS_D} or $repos{'@all'}{DELETE_IS_D} ) and $newsha eq '0' x 40;
|
|
|
|
my @allowed_refs;
|
|
# @all repos: see comments in similar code in check_access
|
|
push @allowed_refs, @ { $repos{$ENV{GL_REPO}}{$ENV{GL_USER}} || [] };
|
|
push @allowed_refs, @ { $repos{'@all'} {$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
|
|
# because making it work screws up efficiency like no tomorrow...
|
|
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), $att_acc);
|
|
&check_ref (\@allowed_refs, $ENV{GL_REPO}, $_ , $att_acc) for @refs;
|
|
|
|
# if we returned at all, all the checks succeeded, so we log the action and exit 0
|
|
|
|
&log_it("$ENV{GL_TS} $att_acc\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
|
|
$UPDATE_CHAINS_TO ||= 'hooks/update.secondary';
|
|
exec $UPDATE_CHAINS_TO, @saved_ARGV
|
|
if -f $UPDATE_CHAINS_TO or -l $UPDATE_CHAINS_TO;
|
|
|
|
exit 0;
|