gitolite/src/lib/Gitolite/Conf/Load.pm

596 lines
16 KiB
Perl
Raw Normal View History

package Gitolite::Conf::Load;
# load conf data from stored files
# ----------------------------------------------------------------------
@EXPORT = qw(
load
2012-03-20 12:13:45 +01:00
access
git_config
2012-03-20 12:13:45 +01:00
option
2012-03-17 02:30:17 +01:00
repo_missing
creator
2012-03-20 12:13:45 +01:00
2012-03-11 03:06:42 +01:00
vrefs
lister_dispatch
);
use Exporter 'import';
use Gitolite::Common;
use Gitolite::Rc;
use strict;
use warnings;
# ----------------------------------------------------------------------
# our variables, because they get loaded by a 'do'
our $data_version = '';
our %repos;
our %one_repo;
our %groups;
our %patterns;
our %configs;
our %one_config;
our %split_conf;
my $subconf = 'master';
my %listers = (
'list-groups' => \&list_groups,
'list-users' => \&list_users,
'list-repos' => \&list_repos,
'list-memberships' => \&list_memberships,
'list-members' => \&list_members,
);
# helps maintain the "cache" in both "load_common" and "load_1"
my $last_repo = '';
# ----------------------------------------------------------------------
{
my $loaded_repo = '';
sub load {
my $repo = shift or _die "load() needs a reponame";
trace( 3, "$repo" );
if ( $repo ne $loaded_repo ) {
load_common();
load_1($repo);
$loaded_repo = $repo;
}
}
}
sub access {
my ( $repo, $user, $aa, $ref ) = @_;
_die "invalid user '$user'" if not( $user and $user =~ $USERNAME_PATT );
sanity($repo);
my @rules;
my $deny_rules;
load($repo);
@rules = rules( $repo, $user );
$deny_rules = option( $repo, 'deny-rules' );
# sanity check the only piece the user can control
_die "invalid characters in ref or filename: '$ref'\n" unless $ref =~ $REF_OR_FILENAME_PATT;
# when a real repo doesn't exist, ^C is a pre-requisite for any other
# check to give valid results.
if ( $aa ne '^C' and $repo !~ /^\@/ and $repo =~ $REPONAME_PATT and repo_missing($repo) ) {
my $iret = access( $repo, $user, '^C', $ref );
$iret =~ s/\^C/$aa/;
return $iret if $iret =~ /DENIED/;
}
# similarly, ^C must be denied if the repo exists
if ( $aa eq '^C' and not repo_missing($repo) ) {
trace( 2, "DENIED by existence" );
return "$aa $ref $repo $user DENIED by existence";
}
trace( 2, scalar(@rules) . " rules found" );
for my $r (@rules) {
2012-03-15 15:34:30 +01:00
my $perm = $r->[1];
my $refex = $r->[2]; $refex =~ s(/USER/)(/$user/);
trace( 3, "perm=$perm, refex=$refex" );
# skip 'deny' rules if the ref is not (yet) known
next if $perm eq '-' and $ref eq 'any' and not $deny_rules;
# rule matches if ref matches or ref is any (see gitolite-shell)
next unless $ref =~ /^$refex/ or $ref eq 'any';
trace( 2, "DENIED by $refex" ) if $perm eq '-';
return "$aa $ref $repo $user DENIED by $refex" if $perm eq '-';
# $perm can be RW\+?(C|D|CD|DC)?M?. $aa can be W, +, C or D, or
# any of these followed by "M".
( my $aaq = $aa ) =~ s/\+/\\+/;
$aaq =~ s/M/.*M/;
# as far as *this* ref is concerned we're ok
return $refex if ( $perm =~ /$aaq/ );
}
trace( 2, "DENIED by fallthru" );
return "$aa $ref $repo $user DENIED by fallthru";
}
sub git_config {
my ( $repo, $key, $empty_values_OK ) = @_;
$key ||= '.';
if (repo_missing($repo)) {
load_common();
} else {
load($repo);
}
# read comments bottom up
my %ret =
# and take the second and third elements to make up your new hash
map { $_->[1] => $_->[2] }
# keep only the ones where the second element matches your key
grep { $_->[1] =~ qr($key) }
# sort this list of listrefs by the first element in each list ref'd to
sort { $a->[0] <=> $b->[0] }
# dereference it (into a list of listrefs)
2012-03-17 02:30:17 +01:00
map { @$_ }
# take the value of that entry
map { $configs{$_} }
# if it has an entry in %configs
grep { $configs{$_} }
# for each "repo" that represents us
2012-03-17 02:30:17 +01:00
memberships( 'repo', $repo );
# %configs looks like this (for each 'foo' that is in memberships())
# 'foo' => [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
# the first map gets you the value
# [ [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ] ],
# the deref gets you
# [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ], [ 8, 'foo.czar', 'jule' ]
# the sort rearranges it (in this case it's already sorted but anyway...)
# the grep gets you this, assuming the key is foo.bar (and "." is regex ".')
# [ 6, 'foo.bar', 'repo' ], [ 7, 'foodbar', 'repoD' ]
# and the final map does this:
# 'foo.bar'=>'repo' , 'foodbar'=>'repoD'
# now some of these will have an empty key; we need to delete them unless
# we're told empty values are OK
unless ($empty_values_OK) {
my($k, $v);
while (($k, $v) = each %ret) {
delete $ret{$k} if not $v;
}
}
my($k, $v);
my $creator = creator($repo);
while (($k, $v) = each %ret) {
$v =~ s/%GL_REPO/$repo/g;
$v =~ s/%GL_CREATOR/$creator/g if $creator;
$ret{$k} = $v;
}
trace( 3, map { ( "$_" => "-> $ret{$_}" ) } ( sort keys %ret ) );
return \%ret;
}
sub option {
my ( $repo, $option ) = @_;
$option = "gitolite-options.$option";
my $ret = git_config( $repo, "^\Q$option\E\$" );
return '' unless %$ret;
return $ret->{$option};
}
sub sanity {
my $repo = shift;
_die "invalid repo '$repo'" if not( $repo and $repo =~ $REPOPATT_PATT );
_die "'$repo' ends with a '/'" if $repo =~ m(/$);
_die "'$repo' contains '..'" if $repo =~ $REPONAME_PATT and $repo =~ m(\.\.);
}
2012-03-17 02:30:17 +01:00
sub repo_missing {
my $repo = shift;
sanity($repo);
2012-03-17 02:30:17 +01:00
return not -d "$rc{GL_REPO_BASE}/$repo.git";
}
# ----------------------------------------------------------------------
sub load_common {
_chdir( $rc{GL_ADMIN_BASE} );
# we take an unusual approach to caching this function!
# (requires that first call to load_common is before first call to load_1)
if ( $last_repo and $split_conf{$last_repo} ) {
delete $repos{$last_repo};
delete $configs{$last_repo};
return;
}
my $cc = "conf/gitolite.conf-compiled.pm";
_die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
if ( data_version_mismatch() ) {
_system("gitolite setup");
_die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
_die "data version update failed; this is serious" if data_version_mismatch();
}
}
sub load_1 {
my $repo = shift;
2012-03-15 01:37:41 +01:00
return if $repo =~ /^\@/;
trace( 3, $repo );
2012-03-17 02:30:17 +01:00
if ( repo_missing($repo) ) {
trace( 1, "repo '$repo' missing" ) if $repo =~ $REPONAME_PATT;
2012-03-17 02:30:17 +01:00
return;
}
_chdir("$rc{GL_REPO_BASE}/$repo.git");
if ( $repo eq $last_repo ) {
$repos{$repo} = $one_repo{$repo};
$configs{$repo} = $one_config{$repo} if $one_config{$repo};
return;
}
if ( -f "gl-conf" ) {
return if not $split_conf{$repo};
my $cc = "./gl-conf";
_die "parse '$cc' failed: " . ( $! or $@ ) unless do $cc;
$last_repo = $repo;
$repos{$repo} = $one_repo{$repo};
$configs{$repo} = $one_config{$repo} if $one_config{$repo};
} else {
_die "split conf set, gl-conf not present for '$repo'" if $split_conf{$repo};
}
}
2012-03-11 03:06:42 +01:00
{
my $lastrepo = '';
my $lastuser = '';
my @cached = ();
2012-03-11 03:06:42 +01:00
sub rules {
my ( $repo, $user ) = @_;
trace( 3, "repo=$repo, user=$user" );
2012-03-11 03:06:42 +01:00
return @cached if ( $lastrepo eq $repo and $lastuser eq $user and @cached );
2012-03-11 03:06:42 +01:00
my @rules = ();
2012-03-17 02:30:17 +01:00
my @repos = memberships( 'repo', $repo );
my @users = memberships( 'user', $user, $repo );
trace( 3, "memberships: " . scalar(@repos) . " repos and " . scalar(@users) . " users found" );
2012-03-11 03:06:42 +01:00
for my $r (@repos) {
for my $u (@users) {
push @rules, @{ $repos{$r}{$u} } if exists $repos{$r} and exists $repos{$r}{$u};
2012-03-11 03:06:42 +01:00
}
}
2012-03-11 03:06:42 +01:00
@rules = sort { $a->[0] <=> $b->[0] } @rules;
$lastrepo = $repo;
$lastuser = $user;
@cached = @rules;
2012-03-11 03:06:42 +01:00
wildrepos almost done (except setperms etc) implementation notes - new sugar role_names() to prefix an "@" to CREATOR, and any role names listed in the rc file. - invalidate the cache in rules() if the repo was missing. Without this, an auto-create operation succeeds the ^C check and calls new_wild_repo(), but then -- due to the cached rules not containing a rule for CREATOR, the actual read/write fails. - treat roles (READERS, WRITERS, etc.) as group names that apply only to that particular repo. Don't add them to %groups, because that would screw up caching, but add them in when memberships() is called for the user. This is why the membership call for the user also has a reponame tacked on -- i.e., a user's membership list varied depending on which repo you're talking about. - while we're about it, pretend we added "CREATOR = <content of gl-creator>" as another "role". Makes things so much easier dealing with "RW+ = CREATOR" - searching for rules pertaining to foo/CREATOR/bar when looking at repo foo/sitaram/bar is done backwards from what g2 used to do. G2 used to play tricks with the do-eval'd file using global variables so that what you get after the do may not even contain 'CREATOR'. We go the other way. We replace sitaram with CREATOR and start looking for memberships of *both* foo/sitaram/bar and foo/CREATOR/bar. - this doesn't work (because we don't know *what* to replace) for missing repos if GL_USER is not set. This means that 'gitolite access ...' queries (which do not set GL_USER) cannot be used reliably for non-existant repos. Since a ^C check is the only meaningful one for a non-existent repo, this means you cannot do that from 'gitolite access'. 'GL_USER=luser gitolite info' will still work though ;-) all in all, much cleaner and simpler than g2.
2012-03-18 02:14:02 +01:00
# however if the repo was missing, invalidate the cache
$lastrepo = '' if repo_missing($repo);
2012-03-11 03:06:42 +01:00
return @rules;
}
2012-03-11 03:06:42 +01:00
sub vrefs {
my ( $repo, $user ) = @_;
# fill the cache if needed
rules( $repo, $user ) unless ( $lastrepo eq $repo and $lastuser eq $user and @cached );
2012-03-11 03:06:42 +01:00
my %seen;
my @vrefs = grep { /^VREF\// and not $seen{$_}++ } map { $_->[2] } @cached;
return @vrefs;
}
}
sub memberships {
trace( 3, @_ );
my ( $type, $base, $repo ) = @_;
$repo ||= '';
my @ret;
my $base2 = '';
@ret = ( $base, '@all' );
2012-03-17 02:30:17 +01:00
if ( $type eq 'repo' ) {
# first, if a repo, say, pub/sitaram/project, has a gl-creator file
# that says "sitaram", find memberships for pub/CREATOR/project also
$base2 = generic_name($base);
# second, you need to check in %repos also
for my $i ( keys %repos, keys %configs ) {
if ( $base eq $i or $base =~ /^$i$/ or $base2 and ( $base2 eq $i or $base2 =~ /^$i$/ ) ) {
push @ret, $i;
}
}
}
push @ret, @{ $groups{$base} } if exists $groups{$base};
push @ret, @{ $groups{$base2} } if $base2 and exists $groups{$base2};
for my $i ( keys %{ $patterns{groups} } ) {
if ( $base =~ /^$i$/ or $base2 and ( $base2 =~ /^$i$/ ) ) {
push @ret, @{ $groups{$i} };
}
}
push @ret, @{ ext_grouplist($base) } if $type eq 'user' and $rc{GROUPLIST_PGM};
if ( $type eq 'user' and $repo and not repo_missing($repo) ) {
# find the roles this user has when accessing this repo and add those
# in as groupnames he is a member of. You need the already existing
# memberships for this; see below this function for an example
push @ret, user_roles( $base, $repo, @ret );
}
2012-03-17 02:30:17 +01:00
@ret = @{ sort_u( \@ret ) };
trace( 3, sort @ret );
return @ret;
}
=for example
conf/gitolite.conf:
@g1 = u1
@g2 = u1
# now user is a member of both g1 and g2
gl-perms for repo being accessed:
READERS @g1
This should result in @READERS being added to the memberships that u1 has
(when accessing this repo). So we send the current list (@g1, @g2) to
user_roles(), otherwise it has to redo that logic.
=cut
sub data_version_mismatch {
return $data_version ne glrc('current-data-version');
}
sub user_roles {
my ( $user, $repo, @eg ) = @_;
# eg == existing groups (that user is already known to be a member of)
my %eg = map { $_ => 1 } @eg;
my %ret = ();
my $f = "$rc{GL_REPO_BASE}/$repo.git/gl-perms";
wildrepos almost done (except setperms etc) implementation notes - new sugar role_names() to prefix an "@" to CREATOR, and any role names listed in the rc file. - invalidate the cache in rules() if the repo was missing. Without this, an auto-create operation succeeds the ^C check and calls new_wild_repo(), but then -- due to the cached rules not containing a rule for CREATOR, the actual read/write fails. - treat roles (READERS, WRITERS, etc.) as group names that apply only to that particular repo. Don't add them to %groups, because that would screw up caching, but add them in when memberships() is called for the user. This is why the membership call for the user also has a reponame tacked on -- i.e., a user's membership list varied depending on which repo you're talking about. - while we're about it, pretend we added "CREATOR = <content of gl-creator>" as another "role". Makes things so much easier dealing with "RW+ = CREATOR" - searching for rules pertaining to foo/CREATOR/bar when looking at repo foo/sitaram/bar is done backwards from what g2 used to do. G2 used to play tricks with the do-eval'd file using global variables so that what you get after the do may not even contain 'CREATOR'. We go the other way. We replace sitaram with CREATOR and start looking for memberships of *both* foo/sitaram/bar and foo/CREATOR/bar. - this doesn't work (because we don't know *what* to replace) for missing repos if GL_USER is not set. This means that 'gitolite access ...' queries (which do not set GL_USER) cannot be used reliably for non-existant repos. Since a ^C check is the only meaningful one for a non-existent repo, this means you cannot do that from 'gitolite access'. 'GL_USER=luser gitolite info' will still work though ;-) all in all, much cleaner and simpler than g2.
2012-03-18 02:14:02 +01:00
my @roles = ();
if ( -f $f ) {
my $fh = _open( "<", $f );
chomp( @roles = <$fh> );
wildrepos almost done (except setperms etc) implementation notes - new sugar role_names() to prefix an "@" to CREATOR, and any role names listed in the rc file. - invalidate the cache in rules() if the repo was missing. Without this, an auto-create operation succeeds the ^C check and calls new_wild_repo(), but then -- due to the cached rules not containing a rule for CREATOR, the actual read/write fails. - treat roles (READERS, WRITERS, etc.) as group names that apply only to that particular repo. Don't add them to %groups, because that would screw up caching, but add them in when memberships() is called for the user. This is why the membership call for the user also has a reponame tacked on -- i.e., a user's membership list varied depending on which repo you're talking about. - while we're about it, pretend we added "CREATOR = <content of gl-creator>" as another "role". Makes things so much easier dealing with "RW+ = CREATOR" - searching for rules pertaining to foo/CREATOR/bar when looking at repo foo/sitaram/bar is done backwards from what g2 used to do. G2 used to play tricks with the do-eval'd file using global variables so that what you get after the do may not even contain 'CREATOR'. We go the other way. We replace sitaram with CREATOR and start looking for memberships of *both* foo/sitaram/bar and foo/CREATOR/bar. - this doesn't work (because we don't know *what* to replace) for missing repos if GL_USER is not set. This means that 'gitolite access ...' queries (which do not set GL_USER) cannot be used reliably for non-existant repos. Since a ^C check is the only meaningful one for a non-existent repo, this means you cannot do that from 'gitolite access'. 'GL_USER=luser gitolite info' will still work though ;-) all in all, much cleaner and simpler than g2.
2012-03-18 02:14:02 +01:00
}
push @roles, "CREATOR = " . creator($repo);
for (@roles) {
# READERS u3 u4 @g1
s/^\s+//; s/ +$//; s/=/ /; s/\s+/ /g; s/^\@//;
next if /^#/;
next unless /\S/;
wildrepos almost done (except setperms etc) implementation notes - new sugar role_names() to prefix an "@" to CREATOR, and any role names listed in the rc file. - invalidate the cache in rules() if the repo was missing. Without this, an auto-create operation succeeds the ^C check and calls new_wild_repo(), but then -- due to the cached rules not containing a rule for CREATOR, the actual read/write fails. - treat roles (READERS, WRITERS, etc.) as group names that apply only to that particular repo. Don't add them to %groups, because that would screw up caching, but add them in when memberships() is called for the user. This is why the membership call for the user also has a reponame tacked on -- i.e., a user's membership list varied depending on which repo you're talking about. - while we're about it, pretend we added "CREATOR = <content of gl-creator>" as another "role". Makes things so much easier dealing with "RW+ = CREATOR" - searching for rules pertaining to foo/CREATOR/bar when looking at repo foo/sitaram/bar is done backwards from what g2 used to do. G2 used to play tricks with the do-eval'd file using global variables so that what you get after the do may not even contain 'CREATOR'. We go the other way. We replace sitaram with CREATOR and start looking for memberships of *both* foo/sitaram/bar and foo/CREATOR/bar. - this doesn't work (because we don't know *what* to replace) for missing repos if GL_USER is not set. This means that 'gitolite access ...' queries (which do not set GL_USER) cannot be used reliably for non-existant repos. Since a ^C check is the only meaningful one for a non-existent repo, this means you cannot do that from 'gitolite access'. 'GL_USER=luser gitolite info' will still work though ;-) all in all, much cleaner and simpler than g2.
2012-03-18 02:14:02 +01:00
my ( $role, @members ) = split;
# role = READERS, members = u3, u4, @g1
if ( $role ne 'CREATOR' and not $rc{ROLES}{$role} ) {
_warn "role '$role' not allowed, ignoring";
next;
}
for my $m (@members) {
if ( $m !~ $USERNAME_PATT ) {
_warn "ignoring '$m' in perms line";
next;
}
wildrepos almost done (except setperms etc) implementation notes - new sugar role_names() to prefix an "@" to CREATOR, and any role names listed in the rc file. - invalidate the cache in rules() if the repo was missing. Without this, an auto-create operation succeeds the ^C check and calls new_wild_repo(), but then -- due to the cached rules not containing a rule for CREATOR, the actual read/write fails. - treat roles (READERS, WRITERS, etc.) as group names that apply only to that particular repo. Don't add them to %groups, because that would screw up caching, but add them in when memberships() is called for the user. This is why the membership call for the user also has a reponame tacked on -- i.e., a user's membership list varied depending on which repo you're talking about. - while we're about it, pretend we added "CREATOR = <content of gl-creator>" as another "role". Makes things so much easier dealing with "RW+ = CREATOR" - searching for rules pertaining to foo/CREATOR/bar when looking at repo foo/sitaram/bar is done backwards from what g2 used to do. G2 used to play tricks with the do-eval'd file using global variables so that what you get after the do may not even contain 'CREATOR'. We go the other way. We replace sitaram with CREATOR and start looking for memberships of *both* foo/sitaram/bar and foo/CREATOR/bar. - this doesn't work (because we don't know *what* to replace) for missing repos if GL_USER is not set. This means that 'gitolite access ...' queries (which do not set GL_USER) cannot be used reliably for non-existant repos. Since a ^C check is the only meaningful one for a non-existent repo, this means you cannot do that from 'gitolite access'. 'GL_USER=luser gitolite info' will still work though ;-) all in all, much cleaner and simpler than g2.
2012-03-18 02:14:02 +01:00
# if user eq u3/u4, or is a member of @g1, he has role READERS
$ret{ '@' . $role } = 1 if $m eq $user or $eg{$m};
}
}
wildrepos almost done (except setperms etc) implementation notes - new sugar role_names() to prefix an "@" to CREATOR, and any role names listed in the rc file. - invalidate the cache in rules() if the repo was missing. Without this, an auto-create operation succeeds the ^C check and calls new_wild_repo(), but then -- due to the cached rules not containing a rule for CREATOR, the actual read/write fails. - treat roles (READERS, WRITERS, etc.) as group names that apply only to that particular repo. Don't add them to %groups, because that would screw up caching, but add them in when memberships() is called for the user. This is why the membership call for the user also has a reponame tacked on -- i.e., a user's membership list varied depending on which repo you're talking about. - while we're about it, pretend we added "CREATOR = <content of gl-creator>" as another "role". Makes things so much easier dealing with "RW+ = CREATOR" - searching for rules pertaining to foo/CREATOR/bar when looking at repo foo/sitaram/bar is done backwards from what g2 used to do. G2 used to play tricks with the do-eval'd file using global variables so that what you get after the do may not even contain 'CREATOR'. We go the other way. We replace sitaram with CREATOR and start looking for memberships of *both* foo/sitaram/bar and foo/CREATOR/bar. - this doesn't work (because we don't know *what* to replace) for missing repos if GL_USER is not set. This means that 'gitolite access ...' queries (which do not set GL_USER) cannot be used reliably for non-existant repos. Since a ^C check is the only meaningful one for a non-existent repo, this means you cannot do that from 'gitolite access'. 'GL_USER=luser gitolite info' will still work though ;-) all in all, much cleaner and simpler than g2.
2012-03-18 02:14:02 +01:00
return keys %ret;
}
sub generic_name {
my $base = shift;
my $base2 = '';
wildrepos almost done (except setperms etc) implementation notes - new sugar role_names() to prefix an "@" to CREATOR, and any role names listed in the rc file. - invalidate the cache in rules() if the repo was missing. Without this, an auto-create operation succeeds the ^C check and calls new_wild_repo(), but then -- due to the cached rules not containing a rule for CREATOR, the actual read/write fails. - treat roles (READERS, WRITERS, etc.) as group names that apply only to that particular repo. Don't add them to %groups, because that would screw up caching, but add them in when memberships() is called for the user. This is why the membership call for the user also has a reponame tacked on -- i.e., a user's membership list varied depending on which repo you're talking about. - while we're about it, pretend we added "CREATOR = <content of gl-creator>" as another "role". Makes things so much easier dealing with "RW+ = CREATOR" - searching for rules pertaining to foo/CREATOR/bar when looking at repo foo/sitaram/bar is done backwards from what g2 used to do. G2 used to play tricks with the do-eval'd file using global variables so that what you get after the do may not even contain 'CREATOR'. We go the other way. We replace sitaram with CREATOR and start looking for memberships of *both* foo/sitaram/bar and foo/CREATOR/bar. - this doesn't work (because we don't know *what* to replace) for missing repos if GL_USER is not set. This means that 'gitolite access ...' queries (which do not set GL_USER) cannot be used reliably for non-existant repos. Since a ^C check is the only meaningful one for a non-existent repo, this means you cannot do that from 'gitolite access'. 'GL_USER=luser gitolite info' will still work though ;-) all in all, much cleaner and simpler than g2.
2012-03-18 02:14:02 +01:00
my $creator;
# get the creator name. For not-yet-born repos this is $ENV{GL_USER},
# which should be set in all cases that we care about, viz., where we are
# checking ^C permissions before new_wild_repo(), and the info command.
# In particular, 'gitolite access' can't be used to check ^C perms on wild
# repos that contain "CREATOR" if GL_USER is not set.
wildrepos almost done (except setperms etc) implementation notes - new sugar role_names() to prefix an "@" to CREATOR, and any role names listed in the rc file. - invalidate the cache in rules() if the repo was missing. Without this, an auto-create operation succeeds the ^C check and calls new_wild_repo(), but then -- due to the cached rules not containing a rule for CREATOR, the actual read/write fails. - treat roles (READERS, WRITERS, etc.) as group names that apply only to that particular repo. Don't add them to %groups, because that would screw up caching, but add them in when memberships() is called for the user. This is why the membership call for the user also has a reponame tacked on -- i.e., a user's membership list varied depending on which repo you're talking about. - while we're about it, pretend we added "CREATOR = <content of gl-creator>" as another "role". Makes things so much easier dealing with "RW+ = CREATOR" - searching for rules pertaining to foo/CREATOR/bar when looking at repo foo/sitaram/bar is done backwards from what g2 used to do. G2 used to play tricks with the do-eval'd file using global variables so that what you get after the do may not even contain 'CREATOR'. We go the other way. We replace sitaram with CREATOR and start looking for memberships of *both* foo/sitaram/bar and foo/CREATOR/bar. - this doesn't work (because we don't know *what* to replace) for missing repos if GL_USER is not set. This means that 'gitolite access ...' queries (which do not set GL_USER) cannot be used reliably for non-existant repos. Since a ^C check is the only meaningful one for a non-existent repo, this means you cannot do that from 'gitolite access'. 'GL_USER=luser gitolite info' will still work though ;-) all in all, much cleaner and simpler than g2.
2012-03-18 02:14:02 +01:00
$creator = creator($base);
$base2 = $base;
$base2 =~ s(\b$creator\b)(CREATOR) if $creator;
wildrepos almost done (except setperms etc) implementation notes - new sugar role_names() to prefix an "@" to CREATOR, and any role names listed in the rc file. - invalidate the cache in rules() if the repo was missing. Without this, an auto-create operation succeeds the ^C check and calls new_wild_repo(), but then -- due to the cached rules not containing a rule for CREATOR, the actual read/write fails. - treat roles (READERS, WRITERS, etc.) as group names that apply only to that particular repo. Don't add them to %groups, because that would screw up caching, but add them in when memberships() is called for the user. This is why the membership call for the user also has a reponame tacked on -- i.e., a user's membership list varied depending on which repo you're talking about. - while we're about it, pretend we added "CREATOR = <content of gl-creator>" as another "role". Makes things so much easier dealing with "RW+ = CREATOR" - searching for rules pertaining to foo/CREATOR/bar when looking at repo foo/sitaram/bar is done backwards from what g2 used to do. G2 used to play tricks with the do-eval'd file using global variables so that what you get after the do may not even contain 'CREATOR'. We go the other way. We replace sitaram with CREATOR and start looking for memberships of *both* foo/sitaram/bar and foo/CREATOR/bar. - this doesn't work (because we don't know *what* to replace) for missing repos if GL_USER is not set. This means that 'gitolite access ...' queries (which do not set GL_USER) cannot be used reliably for non-existant repos. Since a ^C check is the only meaningful one for a non-existent repo, this means you cannot do that from 'gitolite access'. 'GL_USER=luser gitolite info' will still work though ;-) all in all, much cleaner and simpler than g2.
2012-03-18 02:14:02 +01:00
$base2 = '' if $base2 eq $base; # if there was no change
return $base2;
}
wildrepos almost done (except setperms etc) implementation notes - new sugar role_names() to prefix an "@" to CREATOR, and any role names listed in the rc file. - invalidate the cache in rules() if the repo was missing. Without this, an auto-create operation succeeds the ^C check and calls new_wild_repo(), but then -- due to the cached rules not containing a rule for CREATOR, the actual read/write fails. - treat roles (READERS, WRITERS, etc.) as group names that apply only to that particular repo. Don't add them to %groups, because that would screw up caching, but add them in when memberships() is called for the user. This is why the membership call for the user also has a reponame tacked on -- i.e., a user's membership list varied depending on which repo you're talking about. - while we're about it, pretend we added "CREATOR = <content of gl-creator>" as another "role". Makes things so much easier dealing with "RW+ = CREATOR" - searching for rules pertaining to foo/CREATOR/bar when looking at repo foo/sitaram/bar is done backwards from what g2 used to do. G2 used to play tricks with the do-eval'd file using global variables so that what you get after the do may not even contain 'CREATOR'. We go the other way. We replace sitaram with CREATOR and start looking for memberships of *both* foo/sitaram/bar and foo/CREATOR/bar. - this doesn't work (because we don't know *what* to replace) for missing repos if GL_USER is not set. This means that 'gitolite access ...' queries (which do not set GL_USER) cannot be used reliably for non-existant repos. Since a ^C check is the only meaningful one for a non-existent repo, this means you cannot do that from 'gitolite access'. 'GL_USER=luser gitolite info' will still work though ;-) all in all, much cleaner and simpler than g2.
2012-03-18 02:14:02 +01:00
sub creator {
my $repo = shift;
sanity($repo);
wildrepos almost done (except setperms etc) implementation notes - new sugar role_names() to prefix an "@" to CREATOR, and any role names listed in the rc file. - invalidate the cache in rules() if the repo was missing. Without this, an auto-create operation succeeds the ^C check and calls new_wild_repo(), but then -- due to the cached rules not containing a rule for CREATOR, the actual read/write fails. - treat roles (READERS, WRITERS, etc.) as group names that apply only to that particular repo. Don't add them to %groups, because that would screw up caching, but add them in when memberships() is called for the user. This is why the membership call for the user also has a reponame tacked on -- i.e., a user's membership list varied depending on which repo you're talking about. - while we're about it, pretend we added "CREATOR = <content of gl-creator>" as another "role". Makes things so much easier dealing with "RW+ = CREATOR" - searching for rules pertaining to foo/CREATOR/bar when looking at repo foo/sitaram/bar is done backwards from what g2 used to do. G2 used to play tricks with the do-eval'd file using global variables so that what you get after the do may not even contain 'CREATOR'. We go the other way. We replace sitaram with CREATOR and start looking for memberships of *both* foo/sitaram/bar and foo/CREATOR/bar. - this doesn't work (because we don't know *what* to replace) for missing repos if GL_USER is not set. This means that 'gitolite access ...' queries (which do not set GL_USER) cannot be used reliably for non-existant repos. Since a ^C check is the only meaningful one for a non-existent repo, this means you cannot do that from 'gitolite access'. 'GL_USER=luser gitolite info' will still work though ;-) all in all, much cleaner and simpler than g2.
2012-03-18 02:14:02 +01:00
return ( $ENV{GL_USER} || '' ) if repo_missing($repo);
my $f = "$rc{GL_REPO_BASE}/$repo.git/gl-creator";
my $creator = '';
chomp( $creator = slurp($f) ) if -f $f;
return $creator;
}
{
my %cache = ();
sub ext_grouplist {
my $user = shift;
2012-04-18 02:56:18 +02:00
my $pgm = $rc{GROUPLIST_PGM};
return [] if not $pgm;
return $cache{$user} if $cache{$user};
my @extgroups = map { s/^@?/@/; $_; } split ' ', `$rc{GROUPLIST_PGM} $user`;
2012-04-18 02:56:18 +02:00
return ( $cache{$user} = \@extgroups );
}
}
2012-03-08 07:43:50 +01:00
# ----------------------------------------------------------------------
# api functions
# ----------------------------------------------------------------------
sub lister_dispatch {
my $command = shift;
my $fn = $listers{$command} or _die "unknown gitolite sub-command";
return $fn;
}
=for list_groups
Usage: gitolite list-groups
- lists all group names in conf
- no options, no flags
=cut
sub list_groups {
usage() if @_;
2012-03-08 07:43:50 +01:00
load_common();
my @g = ();
while ( my ( $k, $v ) = each(%groups) ) {
push @g, @{$v};
2012-03-08 07:43:50 +01:00
}
return ( sort_u( \@g ) );
2012-03-08 07:43:50 +01:00
}
=for list_users
Usage: gitolite list-users [<repo name pattern>]
List all users and groups explicitly named in a rule. User names not
mentioned in an access rule will not show up; you have to run 'list-members'
on each group name yourself to see them.
WARNING: may be slow if you have thousands of repos. The optional repo name
pattern is an unanchored regex; it can speed things up if you're interested
only in users of a matching set of repos. This is only an optimisation, not
an actual access list; you will still have to pipe it to 'gitolite access'
with appropriate arguments to get an actual access list.
=cut
sub list_users {
my $patt = shift || '.';
usage() if $patt eq '-h' or @_;
my $count = 0;
my $total = 0;
load_common();
my @u = map { keys %{$_} } values %repos;
$total = scalar( grep { /$patt/ } keys %split_conf );
warn "WARNING: you have $total repos to check; this could take some time!\n" if $total > 100;
for my $one ( grep { /$patt/ } keys %split_conf ) {
load_1($one);
$count++; print STDERR "$count / $total\r" if not( $count % 100 ) and timer(5);
push @u, map { keys %{$_} } values %one_repo;
}
print STDERR "\n" if $count >= 100;
return ( sort_u( \@u ) );
}
=for list_repos
Usage: gitolite list-repos
- lists all repos/repo groups in conf
- no options, no flags
=cut
sub list_repos {
usage() if @_;
load_common();
my @r = keys %repos;
push @r, keys %split_conf;
return ( sort_u( \@r ) );
}
=for list_memberships
2012-03-08 12:30:27 +01:00
Usage: gitolite list-memberships <name>
- list all groups a name is a member of
- takes one user/repo name
=cut
2012-03-08 12:30:27 +01:00
sub list_memberships {
usage() if @_ and $_[0] eq '-h' or not @_;
2012-03-08 12:30:27 +01:00
my $name = shift;
2012-03-08 12:30:27 +01:00
load_common();
2012-03-17 02:30:17 +01:00
my @m = memberships( '', $name );
return ( sort_u( \@m ) );
2012-03-08 12:30:27 +01:00
}
=for list_members
2012-03-08 13:29:44 +01:00
Usage: gitolite list-members <group name>
- list all members of a group
- takes one group name
=cut
2012-03-08 13:29:44 +01:00
sub list_members {
usage() if @_ and $_[0] eq '-h' or not @_;
2012-03-08 13:29:44 +01:00
my $name = shift;
2012-03-08 13:29:44 +01:00
load_common();
my @m = ();
while ( my ( $k, $v ) = each(%groups) ) {
for my $g ( @{$v} ) {
2012-03-08 13:29:44 +01:00
push @m, $k if $g eq $name;
}
}
return ( sort_u( \@m ) );
2012-03-08 13:29:44 +01:00
}
# ----------------------------------------------------------------------
{
my $start_time = 0;
sub timer {
unless ($start_time) {
$start_time = time();
return 0;
}
my $elapsed = shift;
return 0 if time() - $start_time < $elapsed;
$start_time = time();
return 1;
}
}
1;