2012-03-08 09:00:13 +01:00
|
|
|
package Gitolite::Conf::Store;
|
|
|
|
|
|
|
|
# receive parsed conf data and store it
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
|
|
@EXPORT = qw(
|
|
|
|
add_to_group
|
|
|
|
set_repolist
|
|
|
|
parse_refs
|
|
|
|
parse_users
|
|
|
|
add_rule
|
2012-03-16 09:59:45 +01:00
|
|
|
add_config
|
2012-03-08 09:00:13 +01:00
|
|
|
set_subconf
|
2012-03-16 09:59:45 +01:00
|
|
|
|
|
|
|
expand_list
|
2012-03-08 09:00:13 +01:00
|
|
|
new_repos
|
|
|
|
new_repo
|
2012-03-17 02:30:17 +01:00
|
|
|
new_wild_repo
|
2012-03-08 09:00:13 +01:00
|
|
|
hook_repos
|
|
|
|
store
|
2012-03-11 14:00:06 +01:00
|
|
|
parse_done
|
2012-03-08 09:00:13 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
use Exporter 'import';
|
|
|
|
use Data::Dumper;
|
|
|
|
$Data::Dumper::Indent = 1;
|
|
|
|
$Data::Dumper::Sortkeys = 1;
|
|
|
|
|
|
|
|
use Gitolite::Common;
|
|
|
|
use Gitolite::Rc;
|
|
|
|
use Gitolite::Hooks::Update;
|
|
|
|
use Gitolite::Hooks::PostUpdate;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
|
|
my %repos;
|
|
|
|
my %groups;
|
|
|
|
my %configs;
|
|
|
|
my %split_conf;
|
|
|
|
|
|
|
|
my @repolist; # current repo list; reset on each 'repo ...' line
|
|
|
|
my $subconf = 'master';
|
2012-03-16 09:59:45 +01:00
|
|
|
my $nextseq = 0;
|
2012-03-08 09:00:13 +01:00
|
|
|
my %ignored;
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
|
|
sub add_to_group {
|
|
|
|
my ( $lhs, @rhs ) = @_;
|
|
|
|
_die "bad group '$lhs'" unless $lhs =~ $REPONAME_PATT;
|
2012-03-14 09:41:43 +01:00
|
|
|
map { _die "bad expansion '$_'" unless $_ =~ $REPOPATT_PATT } @rhs;
|
2012-03-08 09:00:13 +01:00
|
|
|
|
|
|
|
# store the group association, but overload it to keep track of when
|
|
|
|
# the group was *first* created by using $subconf as the *value*
|
|
|
|
do { $groups{$lhs}{$_} ||= $subconf }
|
|
|
|
for ( expand_list(@rhs) );
|
|
|
|
|
|
|
|
# create the group hash even if empty
|
|
|
|
$groups{$lhs} = {} unless $groups{$lhs};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub set_repolist {
|
|
|
|
@repolist = @_;
|
|
|
|
|
|
|
|
# ...sanity checks
|
|
|
|
for (@repolist) {
|
|
|
|
_warn "explicit '.git' extension ignored for $_.git" if s/\.git$//;
|
|
|
|
_die "bad reponame '$_'" if $_ !~ $REPOPATT_PATT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub parse_refs {
|
|
|
|
my $refs = shift;
|
|
|
|
my @refs; @refs = split( ' ', $refs ) if $refs;
|
|
|
|
@refs = expand_list(@refs);
|
|
|
|
|
|
|
|
# if no ref is given, this PERM applies to all refs
|
|
|
|
@refs = qw(refs/.*) unless @refs;
|
|
|
|
|
2012-03-11 03:06:42 +01:00
|
|
|
# fully qualify refs that dont start with "refs/" or "VREF/";
|
2012-03-08 09:00:13 +01:00
|
|
|
# prefix them with "refs/heads/"
|
2012-03-11 03:06:42 +01:00
|
|
|
@refs = map { m(^(refs|VREF)/) or s(^)(refs/heads/); $_ } @refs;
|
2012-03-08 09:00:13 +01:00
|
|
|
|
|
|
|
return @refs;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub parse_users {
|
|
|
|
my $users = shift;
|
|
|
|
my @users = split ' ', $users;
|
|
|
|
do { _die "bad username '$_'" unless $_ =~ $USERNAME_PATT }
|
|
|
|
for @users;
|
|
|
|
|
|
|
|
return @users;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub add_rule {
|
|
|
|
my ( $perm, $ref, $user ) = @_;
|
2012-03-15 15:34:30 +01:00
|
|
|
_die "bad ref '$ref'" unless $ref =~ $REPOPATT_PATT;
|
2012-03-14 09:41:43 +01:00
|
|
|
_die "bad user '$user'" unless $user =~ $USERNAME_PATT;
|
2012-03-08 09:00:13 +01:00
|
|
|
|
2012-03-16 09:59:45 +01:00
|
|
|
$nextseq++;
|
2012-03-08 09:00:13 +01:00
|
|
|
for my $repo (@repolist) {
|
|
|
|
if ( check_subconf_repo_disallowed( $subconf, $repo ) ) {
|
|
|
|
my $repo = $repo;
|
|
|
|
$repo =~ s/^\@$subconf\./locally modified \@/;
|
|
|
|
$ignored{$subconf}{$repo} = 1;
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2012-03-16 09:59:45 +01:00
|
|
|
push @{ $repos{$repo}{$user} }, [ $nextseq, $perm, $ref ];
|
2012-03-08 09:00:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-16 09:59:45 +01:00
|
|
|
sub add_config {
|
2012-03-17 02:30:17 +01:00
|
|
|
my ( $n, $key, $value ) = @_;
|
2012-03-16 09:59:45 +01:00
|
|
|
|
|
|
|
$nextseq++;
|
|
|
|
for my $repo (@repolist) {
|
|
|
|
push @{ $configs{$repo} }, [ $nextseq, $key, $value ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-08 09:00:13 +01:00
|
|
|
sub set_subconf {
|
|
|
|
$subconf = shift;
|
2012-03-14 09:41:43 +01:00
|
|
|
_die "bad subconf '$subconf'" unless $subconf =~ /^[-\w.]+$/;
|
2012-03-08 09:00:13 +01:00
|
|
|
}
|
|
|
|
|
2012-03-16 09:59:45 +01:00
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
|
|
sub expand_list {
|
|
|
|
my @list = @_;
|
|
|
|
my @new_list = ();
|
|
|
|
|
|
|
|
for my $item (@list) {
|
|
|
|
if ( $item =~ /^@/ and $item ne '@all' ) # nested group
|
|
|
|
{
|
|
|
|
_die "undefined group $item" unless $groups{$item};
|
|
|
|
# add those names to the list
|
|
|
|
push @new_list, sort keys %{ $groups{$item} };
|
|
|
|
} else {
|
|
|
|
push @new_list, $item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return @new_list;
|
|
|
|
}
|
|
|
|
|
2012-03-08 09:00:13 +01:00
|
|
|
sub new_repos {
|
|
|
|
trace(3);
|
2012-03-16 05:29:45 +01:00
|
|
|
_chdir( $rc{GL_REPO_BASE} );
|
2012-03-08 09:00:13 +01:00
|
|
|
|
|
|
|
# normal repos
|
|
|
|
my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos;
|
|
|
|
# add in members of repo groups
|
|
|
|
map { push @repos, keys %{ $groups{$_} } } grep { /^@/ } keys %repos;
|
|
|
|
|
2012-03-16 05:29:45 +01:00
|
|
|
for my $repo ( @{ sort_u( \@repos ) } ) {
|
2012-03-08 09:00:13 +01:00
|
|
|
next unless $repo =~ $REPONAME_PATT; # skip repo patterns
|
|
|
|
next if $repo =~ m(^\@|EXTCMD/); # skip groups and fake repos
|
|
|
|
|
|
|
|
new_repo($repo) if not -d "$repo.git";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub new_repo {
|
|
|
|
my $repo = shift;
|
2012-03-15 16:30:39 +01:00
|
|
|
trace( 3, $repo );
|
2012-03-08 09:00:13 +01:00
|
|
|
|
|
|
|
_mkdir("$repo.git");
|
|
|
|
_chdir("$repo.git");
|
2012-03-10 14:26:29 +01:00
|
|
|
_system("git init --bare >&2");
|
2012-03-16 05:29:45 +01:00
|
|
|
_chdir( $rc{GL_REPO_BASE} );
|
2012-03-08 09:00:13 +01:00
|
|
|
hook_1($repo);
|
|
|
|
}
|
|
|
|
|
2012-03-17 02:30:17 +01:00
|
|
|
sub new_wild_repo {
|
|
|
|
my ( $repo, $user ) = @_;
|
|
|
|
_chdir( $rc{GL_REPO_BASE} );
|
2012-03-19 03:01:09 +01:00
|
|
|
|
2012-03-19 03:19:01 +01:00
|
|
|
trigger( 'PRE_CREATE', $repo, $user );
|
2012-03-17 02:30:17 +01:00
|
|
|
new_repo($repo);
|
|
|
|
_print( "$repo.git/gl-creator", $user );
|
2012-03-17 07:55:38 +01:00
|
|
|
_print( "$repo.git/gl-perms", "$rc{DEFAULT_ROLE_PERMS}\n" ) if $rc{DEFAULT_ROLE_PERMS};
|
2012-03-19 03:19:01 +01:00
|
|
|
trigger( 'POST_CREATE', $repo, $user );
|
2012-03-19 03:01:09 +01:00
|
|
|
|
2012-03-17 02:30:17 +01:00
|
|
|
_chdir( $rc{GL_ADMIN_BASE} );
|
|
|
|
}
|
|
|
|
|
2012-03-08 09:00:13 +01:00
|
|
|
sub hook_repos {
|
|
|
|
trace(3);
|
|
|
|
# all repos, all hooks
|
2012-03-16 05:29:45 +01:00
|
|
|
_chdir( $rc{GL_REPO_BASE} );
|
2012-03-08 09:00:13 +01:00
|
|
|
|
|
|
|
for my $repo (`find . -name "*.git" -prune`) {
|
|
|
|
chomp($repo);
|
|
|
|
$repo =~ s/\.git$//;
|
2012-03-30 20:27:54 +02:00
|
|
|
$repo =~ s(^\./)();
|
2012-03-08 09:00:13 +01:00
|
|
|
hook_1($repo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub store {
|
|
|
|
trace(3);
|
|
|
|
|
|
|
|
# first write out the ones for the physical repos
|
2012-03-16 05:29:45 +01:00
|
|
|
_chdir( $rc{GL_REPO_BASE} );
|
2012-03-08 14:50:00 +01:00
|
|
|
my $phy_repos = list_phy_repos(1);
|
|
|
|
|
2012-03-16 05:29:45 +01:00
|
|
|
for my $repo ( @{$phy_repos} ) {
|
2012-03-08 09:00:13 +01:00
|
|
|
store_1($repo);
|
|
|
|
}
|
|
|
|
|
2012-03-16 05:29:45 +01:00
|
|
|
_chdir( $rc{GL_ADMIN_BASE} );
|
2012-03-08 09:00:13 +01:00
|
|
|
store_common();
|
|
|
|
}
|
|
|
|
|
2012-03-11 14:00:06 +01:00
|
|
|
sub parse_done {
|
2012-03-12 16:24:30 +01:00
|
|
|
for my $ig ( sort keys %ignored ) {
|
2012-03-30 14:30:02 +02:00
|
|
|
_warn "subconf '$ig' attempting to set access for " . join( ", ", sort keys %{ $ignored{$ig} } );
|
2012-03-11 14:00:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-08 09:00:13 +01:00
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
|
|
sub check_subconf_repo_disallowed {
|
|
|
|
# trying to set access for $repo (='foo')...
|
|
|
|
my ( $subconf, $repo ) = @_;
|
2012-03-15 16:30:39 +01:00
|
|
|
trace( 2, $subconf, $repo );
|
2012-03-08 09:00:13 +01:00
|
|
|
|
|
|
|
# processing the master config, not a subconf
|
|
|
|
return 0 if $subconf eq 'master';
|
|
|
|
# subconf is also called 'foo' (you're allowed to have a
|
|
|
|
# subconf that is only concerned with one repo)
|
|
|
|
return 0 if $subconf eq $repo;
|
|
|
|
# same thing in big-config-land; foo is just @foo now
|
|
|
|
return 0 if ( "\@$subconf" eq $repo );
|
|
|
|
my @matched = grep { $repo =~ /^$_$/ }
|
|
|
|
grep { $groups{"\@$subconf"}{$_} eq 'master' }
|
|
|
|
sort keys %{ $groups{"\@$subconf"} };
|
|
|
|
return 0 if @matched > 0;
|
|
|
|
|
2012-03-15 16:30:39 +01:00
|
|
|
trace( 2, "-> disallowed" );
|
2012-03-08 09:00:13 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub store_1 {
|
|
|
|
# warning: writes and *deletes* it from %repos and %configs
|
|
|
|
my ($repo) = shift;
|
2012-03-15 16:30:39 +01:00
|
|
|
trace( 3, $repo );
|
2012-03-08 09:00:13 +01:00
|
|
|
return unless $repos{$repo} and -d "$repo.git";
|
|
|
|
|
|
|
|
my ( %one_repo, %one_config );
|
|
|
|
|
|
|
|
open( my $compiled_fh, ">", "$repo.git/gl-conf" ) or return;
|
|
|
|
|
|
|
|
$one_repo{$repo} = $repos{$repo};
|
|
|
|
delete $repos{$repo};
|
|
|
|
my $dumped_data = Data::Dumper->Dump( [ \%one_repo ], [qw(*one_repo)] );
|
|
|
|
|
|
|
|
if ( $configs{$repo} ) {
|
|
|
|
$one_config{$repo} = $configs{$repo};
|
|
|
|
delete $configs{$repo};
|
|
|
|
$dumped_data .= Data::Dumper->Dump( [ \%one_config ], [qw(*one_config)] );
|
|
|
|
}
|
|
|
|
|
|
|
|
print $compiled_fh $dumped_data;
|
|
|
|
close $compiled_fh;
|
|
|
|
|
|
|
|
$split_conf{$repo} = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub store_common {
|
2012-03-15 16:30:39 +01:00
|
|
|
trace(3);
|
2012-03-08 09:00:13 +01:00
|
|
|
my $cc = "conf/gitolite.conf-compiled.pm";
|
|
|
|
my $compiled_fh = _open( ">", "$cc.new" );
|
|
|
|
|
2012-03-16 05:29:45 +01:00
|
|
|
my $data_version = glrc('current-data-version');
|
2012-03-30 02:41:06 +02:00
|
|
|
trace( 3, "data_version = $data_version" );
|
2012-03-08 09:00:13 +01:00
|
|
|
print $compiled_fh Data::Dumper->Dump( [$data_version], [qw(*data_version)] );
|
|
|
|
|
|
|
|
my $dumped_data = Data::Dumper->Dump( [ \%repos ], [qw(*repos)] );
|
|
|
|
$dumped_data .= Data::Dumper->Dump( [ \%configs ], [qw(*configs)] ) if %configs;
|
|
|
|
|
|
|
|
print $compiled_fh $dumped_data;
|
|
|
|
|
|
|
|
if (%groups) {
|
|
|
|
my %groups = %{ inside_out( \%groups ) };
|
|
|
|
$dumped_data = Data::Dumper->Dump( [ \%groups ], [qw(*groups)] );
|
|
|
|
print $compiled_fh $dumped_data;
|
|
|
|
}
|
|
|
|
print $compiled_fh Data::Dumper->Dump( [ \%split_conf ], [qw(*split_conf)] ) if %split_conf;
|
|
|
|
|
|
|
|
close $compiled_fh or _die "close compiled-conf failed: $!\n";
|
|
|
|
rename "$cc.new", $cc;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
my $hook_reset = 0;
|
|
|
|
|
|
|
|
sub hook_1 {
|
|
|
|
my $repo = shift;
|
2012-03-15 16:30:39 +01:00
|
|
|
trace( 3, $repo );
|
2012-03-08 09:00:13 +01:00
|
|
|
|
|
|
|
# reset the gitolite supplied hooks, in case someone fiddled with
|
|
|
|
# them, but only once per run
|
|
|
|
if ( not $hook_reset ) {
|
2012-03-16 05:29:45 +01:00
|
|
|
_mkdir("$rc{GL_ADMIN_BASE}/hooks/common");
|
|
|
|
_mkdir("$rc{GL_ADMIN_BASE}/hooks/gitolite-admin");
|
|
|
|
_print( "$rc{GL_ADMIN_BASE}/hooks/common/update", update_hook() );
|
|
|
|
_print( "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin/post-update", post_update_hook() );
|
|
|
|
chmod 0755, "$rc{GL_ADMIN_BASE}/hooks/common/update";
|
|
|
|
chmod 0755, "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin/post-update";
|
2012-03-08 09:00:13 +01:00
|
|
|
$hook_reset++;
|
|
|
|
}
|
|
|
|
|
|
|
|
# propagate user hooks
|
2012-03-16 05:29:45 +01:00
|
|
|
ln_sf( "$rc{GL_ADMIN_BASE}/hooks/common", "*", "$repo.git/hooks" );
|
2012-03-08 09:00:13 +01:00
|
|
|
|
|
|
|
# propagate admin hook
|
2012-03-16 05:29:45 +01:00
|
|
|
ln_sf( "$rc{GL_ADMIN_BASE}/hooks/gitolite-admin", "*", "$repo.git/hooks" ) if $repo eq 'gitolite-admin';
|
2012-03-08 09:00:13 +01:00
|
|
|
|
|
|
|
# g2 diff: no "site-wide" hooks (the stuff in between gitolite hooks
|
|
|
|
# and user hooks) anymore. I don't think anyone used them anyway...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub inside_out {
|
|
|
|
my $href = shift;
|
|
|
|
# input conf: @aa = bb cc <newline> @bb = @aa dd
|
|
|
|
|
|
|
|
my %ret = ();
|
|
|
|
while ( my ( $k, $v ) = each( %{$href} ) ) {
|
|
|
|
# $k is '@aa', $v is a href
|
|
|
|
for my $k2 ( keys %{$v} ) {
|
|
|
|
# $k2 is bb, then cc
|
|
|
|
push @{ $ret{$k2} }, $k;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return \%ret;
|
|
|
|
# %groups = ( 'bb' => [ '@bb', '@aa' ], 'cc' => [ '@bb', '@aa' ], 'dd' => [ '@bb' ]);
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|
|
|
|
|