Merge branch 'pu-big-config' into pu

This commit is contained in:
Sitaram Chamarty 2010-05-18 15:40:18 +05:30
commit c3d23f8734
6 changed files with 488 additions and 138 deletions

View file

@ -82,7 +82,13 @@ $GIT_PATH="";
# --------------------------------------
# ----------------------------------------------------------------------
# BIG CONFIG SETTINGS
# Please read doc/big-config.mkd for details
$GL_BIG_CONFIG = 0;
$GL_NO_DAEMON_NO_GITWEB = 0;
# ----------------------------------------------------------------------
# SECURITY SENSITIVE SETTINGS

219
doc/big-config.mkd Normal file
View file

@ -0,0 +1,219 @@
# what is a "big-config"
In this document:
* when/why do we need it?
* how do we use it?
* summary of settings in RC file
* what are the downsides?
* (extra coolness) usergroups and LDAP/similar tools
### when/why do we need it?
A "big config" is anything that has a few thousand users and a few thousand
repos, organised into groups that are much smaller in number (like maybe a few
hundreds of repogroups and a few dozens of usergroups).
So let's say you have
@wbr = lynx firefox
@devs = alice bob
repo @wbr
RW+ next = @devs
RW master = @devs
Gitolite internally translates this to
repo lynx firefox
RW+ next = alice bob
RW master = alice bob
Not just that -- it now generates the actual config rules once for each
user-repo-ref combination (there are 8 combinations above; the compiled config
file looks partly like this:
%repos = (
'firefox' => {
'R' => {
'alice' => 1,
'bob' => 1
},
'W' => {
'alice' => 1,
'bob' => 1
},
'alice' => [
{
'refs/heads/next' => 'RW+'
},
{
'refs/heads/master' => 'RW'
}
],
'bob' => [
{
'refs/heads/next' => 'RW+'
},
{
'refs/heads/master' => 'RW'
}
]
},
'lynx' => {
'R' => {
'alice' => 1,
'bob' => 1
},
'W' => {
'alice' => 1,
'bob' => 1
},
'alice' => [
{
'refs/heads/next' => 'RW+'
},
{
'refs/heads/master' => 'RW'
}
],
'bob' => [
{
'refs/heads/next' => 'RW+'
},
{
'refs/heads/master' => 'RW'
}
]
}
);
Phew!
You can imagine what that does when you have 10,000 users and 10,000 repos.
Let's just say it's not pretty :)
### how do we use it?
Now, if you had all those 10,000 users and repos explicitly listed (no
groups), then there is no help. But if, like the above example, you had
groups like we used above, there is hope.
Just set
$GL_BIG_CONFIG = 1;
in the `~/.gitolite.rc` file on the server. When you do that, and push this
configuration, the compiled file looks like this:
%repos = (
'@wbr' => {
'@devs' => [
{
'refs/heads/next' => 'RW+'
},
{
'refs/heads/master' => 'RW'
}
],
'R' => {
'@devs' => 1
},
'W' => {
'@devs' => 1
}
},
);
%groups = (
'@devs' => {
'alice' => 'master',
'bob' => 'master'
},
'@wbr' => {
'firefox' => 'master',
'lynx' => 'master'
}
);
That's a lot smaller, and allows orders of magintude more repos and groups to
be supported.
### summary of settings in RC file
The default RC file contains the following lines:
$GL_BIG_CONFIG = 0;
$GL_NO_DAEMON_NO_GITWEB = 0;
The first setting means that by default, big-config is off; you can change it
to 1 to enable it.
The second is a very useful optimisation that you *must* enable if you *do*
have a large number of repositories, and do *not* use gitolite's support for
gitweb or git-daemon access (see "[easier to specify gitweb description and
gitweb/daemon access][gw]" for details). This will save a lot of time when
you push the gitolite-admin repo with changes.
[gw]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#gitweb
### what are the downsides?
There are a few minor issues.
First, "deny" rules (rules whose "permission" is a "-" sign), will not work
exactly the same as before.
[TODO: add a nice example etc...]
Second, if you use the delegation feature, you can no longer define or extend
@groups in a fragment, for security reasons. It will also not let you use any
group other than the @fragname itself (specifically, groups which contained a
subset of the allowed @fragname, which would work normally, do not work now).
(If you didn't understand all that, you're probably not using delegation, so
feel free to ignore it!)
### (extra coolness) usergroups and LDAP/similar tools
[Please NOTE: this is all about *user* groups, not *repo* groups]
Gitolite now allows usergroup information to be passed in from outside. The
`gl-auth-commmand` can now take an optional list of usergroup names after the
first argument (which is the username).
To understand why this is useful, consider the following:
Some people have an LDAP-backed ssh daemon (or some other similar mechanism
that can speak "ssh" to the client), with pubkeys stored in LDAP, etc., and
some way (not using `~/.ssh/authorized_keys`) of invoking gitolite.
Such setups also have "usergroups", and a way to tell, for each user, what
groups he/she is a member of. So let's say "alice" works on projects "foo"
and "bar", while "bob" is works on project "bar" and is a member of the
`L3_support` team.
You can use those group names in the gitolite config file for access control
as "@foo", "@bar", `@L3_support`, etc.; please note the "@" prefix because
gitolite requires it.
However, that still leaves a wee little inconvenience. You still have to add
lines like this to the gitolite config file:
@foo = alice
@bar = alice bob
@L3_support = bob
You don't need to do that anymore now. Tell your authentication system that,
after authenticating alice, instead of running:
/some/path/to/gl-auth-command alice
it should first find the groups that alice is a member of, and then run:
/some/path/to/gl-auth-command alice foo bar
That's it. Internally, gitolite will behave as if the config file had also
specified:
@foo = alice
@bar = alice

View file

@ -34,27 +34,12 @@ 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};
# then "do" the compiled config file, whose name we now know. Before doing
# that we setup the creator 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 $creator = $ENV{GL_CREATOR};
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";
my ($perm, $creator, $wild) = &repo_rights($ENV{GL_REPO});
my $reported_repo = $ENV{GL_REPO} . ( $ENV{GL_REPOPATT} ? " ($ENV{GL_REPOPATT})" : "" );
# ----------------------------------------------------------------------------
# start...
# ----------------------------------------------------------------------------
@ -71,17 +56,17 @@ 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';
# 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
$perm = '+' if $ref =~ m(refs/tags/) and $oldsha ne ('0' x 40);
$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
$perm = '+' if $oldsha ne $merge_base;
$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'
$perm = 'D' if ( $repos{$ENV{GL_REPO}}{DELETE_IS_D} or $repos{'@all'}{DELETE_IS_D} ) and $newsha eq '0' x 40;
$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
@ -113,12 +98,12 @@ if (exists $repos{$ENV{GL_REPO}}{NAME_LIMITS}) {
# 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;
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} $perm\t" .
&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");

View file

@ -1,4 +1,7 @@
use strict;
use Data::Dumper;
$Data::Dumper::Deepcopy = 1;
# this file is commonly used using "require". It is not required to use "use"
# (because it doesn't live in a different package)
@ -34,8 +37,9 @@ our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # very simple patter
our $REPOPATT_PATT=qr(^\@?[0-9a-zA-Z][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$);
# these come from the RC file
our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS, $REPO_BASE, $GL_CONF_COMPILED);
our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS, $REPO_BASE, $GL_CONF_COMPILED, $GL_BIG_CONFIG);
our %repos;
our %groups;
# ----------------------------------------------------------------------------
# convenience subs
@ -253,24 +257,39 @@ sub parse_acl
# want the config dumped as is, really
return unless $repo;
# return with "no wildcard match" status if you found the actual repo in
# the config or if wild is unset
return $ENV{GL_REPOPATT} = "" if $repos{$repo} or not $GL_WILDREPOS;
my ($wild, @repo_plus, @user_plus);
# expand $repo and $gl_user into all possible matching values
($wild, @repo_plus) = &get_memberships($repo, 1);
( @user_plus) = &get_memberships($gl_user, 0);
# XXX testing notes: the above should return just one entry during
# non-BC usage, whether wild or not
die "assert 1 failed" if (@repo_plus > 1 and $repo_plus[-1] ne '@all'
or @repo_plus > 2) and not $GL_BIG_CONFIG;
# didn't find actual repo in %repos, and wild is set, so find the repo
# pattern that matches the actual repo
my @matched = grep { $repo =~ /^$_$/ } sort keys %repos;
# the old "convenience copy" thing. Now on steroids :)
# didn't find a match? avoid leaking info to user about repo existence;
# as before, pretend "no wildcard match" status
return $ENV{GL_REPOPATT} = "" unless @matched;
# note that when copying the @all entry, we retain the destination name as
# @all; we dont change it to $repo or $gl_user
for my $r ('@all', @repo_plus) {
my $dr = $repo; $dr = '@all' if $r eq '@all';
$repos{$dr}{DELETE_IS_D} = 1 if $repos{$r}{DELETE_IS_D};
$repos{$dr}{NAME_LIMITS} = 1 if $repos{$r}{NAME_LIMITS};
die "$repo has multiple matches\n@matched\n" if @matched > 1;
for my $u ('@all', @user_plus) {
my $du = $gl_user; $du = '@all' if $u eq '@all';
$repos{$dr}{C}{$du} = 1 if $repos{$r}{C}{$u};
$repos{$dr}{R}{$du} = 1 if $repos{$r}{R}{$u};
$repos{$dr}{W}{$du} = 1 if $repos{$r}{W}{$u};
# found exactly one pattern that matched, copy its ACL for convenience
$repos{$repo} = $repos{$matched[0]};
# and return the pattern
return $ENV{GL_REPOPATT} = $matched[0];
next if $r eq $dr and $u eq $du; # no point duplicating those refexes
push @{ $repos{$dr}{$du} }, @{ $repos{$r}{$u} }
if exists $repos{$r}{$u} and ref($repos{$r}{$u}) eq 'ARRAY';
}
}
$ENV{GL_REPOPATT} = "";
$ENV{GL_REPOPATT} = $wild if $wild and $GL_WILDREPOS;
return ($wild);
}
# ----------------------------------------------------------------------------
@ -295,6 +314,11 @@ sub report_basic
&report_version($GL_ADMINDIR, $user);
print "\rthe gitolite config gives you the following access:\r\n";
for my $r (sort keys %repos) {
if ($r =~ $REPONAME_PATT) {
&parse_acl($GL_CONF_COMPILED, $r, "NOBODY", "NOBODY", "NOBODY");
} else {
&parse_acl($GL_CONF_COMPILED, $r, $ENV{GL_USER}, "NOBODY", "NOBODY");
}
# @all repos; meaning of read/write flags:
# @R => @all users are allowed access to this repo
# #R => you're a super user and can see @all repos
@ -331,7 +355,7 @@ sub expand_wild
# actual_repo has to match the pattern being expanded
next unless $actual_repo =~ /$repo/;
my($perm, $creator) = &repo_rights($actual_repo);
my($perm, $creator, $wild) = &repo_rights($actual_repo);
next unless $perm =~ /\S/;
print "$perm\t$creator\t$actual_repo\n";
}
@ -342,64 +366,67 @@ sub expand_wild
# how/why). Regardless of how we're called, we assume $ENV{GL_USER} is
# already defined
{
my %normal_repos;
my $last_repo = '';
sub repo_rights {
my $repo = shift;
$repo =~ s/^\.\///;
$repo =~ s/\.git$//;
return if $last_repo eq $repo; # a wee bit o' caching, though not yet needed
# we get passed an actual repo name. It may be a normal
# (non-wildcard) repo, in which case it is assumed to exist. If it's
# a wildrepo, it may or may not exist. If it doesn't exist, the "C"
# perms are also filled in, else that column is left blank
unless (%normal_repos) {
unless ($REPO_BASE) {
# means we've been called from outside
# means we've been called from outside; see doc/admin-defined-commands.mkd
&where_is_rc();
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
}
&parse_acl($GL_CONF_COMPILED, "", "NOBODY", "NOBODY", "NOBODY");
%normal_repos = %repos;
}
my $creator;
my $perm = ' ';
my $creator;
# if repo is present "as is" in the config, those permissions will
# override anything inherited from a wildcard that may have matched
if ($normal_repos{$repo}) {
%repos = %normal_repos;
$creator = '<gitolite>';
} elsif ( -d "$ENV{GL_REPO_BASE_ABS}/$repo.git" ) {
# must be a wildrepo, and it has already been created; find the
# creator and subsitute in repos
# get basic info about the repo and fill %repos
my $wild = '';
my $exists = -d "$ENV{GL_REPO_BASE_ABS}/$repo.git";
if ($exists) {
# these will be empty if it's not a wildcard repo anyway
my ($read, $write);
($creator, $read, $write) = &wild_repo_rights($ENV{GL_REPO_BASE_ABS}, $repo, $ENV{GL_USER});
# get access list with these substitutions
&parse_acl($GL_CONF_COMPILED, $repo, $creator || "NOBODY", $read || "NOBODY", $write || "NOBODY");
$wild = &parse_acl($GL_CONF_COMPILED, $repo, $creator || "NOBODY", $read || "NOBODY", $write || "NOBODY");
} else {
$wild = &parse_acl($GL_CONF_COMPILED, $repo, $ENV{GL_USER}, "NOBODY", "NOBODY");
}
if ($exists and not $wild) {
$creator = '<gitolite>';
} elsif ($exists) {
# is a wildrepo, and it has already been created
$creator = "($creator)";
} else {
# repo didn't exist; C perms also need to be filled in after
# getting access list with only creator filled in
&parse_acl($GL_CONF_COMPILED, $repo, $ENV{GL_USER}, "NOBODY", "NOBODY");
# repo didn't exist; C perms need to be filled in
$perm = ( $repos{$repo}{C}{'@all'} ? ' @C' : ( $repos{$repo}{C}{$ENV{GL_USER}} ? ' =C' : ' ' )) if $GL_WILDREPOS;
# if you didn't have perms to create it, delete the "convenience"
# copy of the ACL that parse_acl makes
delete $repos{$repo} unless $perm =~ /C/;
$creator = "<repo_not_found>";
$creator = "<notfound>";
}
$perm .= ( $repos{$repo}{R}{'@all'} ? ' @R' : ( $repos{'@all'}{R}{$ENV{GL_USER}} ? ' #R' : ( $repos{$repo}{R}{$ENV{GL_USER}} ? ' R' : ' ' )));
$perm .= ( $repos{$repo}{W}{'@all'} ? ' @W' : ( $repos{'@all'}{W}{$ENV{GL_USER}} ? ' #W' : ( $repos{$repo}{W}{$ENV{GL_USER}} ? ' W' : ' ' )));
return($perm, $creator);
# set up for caching %repos
$last_repo = $repo;
return($perm, $creator, $wild);
}
}
# helper/convenience routine to get rights and ownership from a shell command
sub cli_repo_rights {
my ($perm, $creator) = &repo_rights($_[0]);
my ($perm, $creator, $wild) = &repo_rights($_[0]);
$perm =~ s/ /_/g;
$creator =~ s/^\(|\)$//g;
print "$perm $creator\n";
@ -443,6 +470,84 @@ sub special_cmd
}
}
# ----------------------------------------------------------------------------
# get memberships
# ----------------------------------------------------------------------------
# given a plain reponame or username, return:
# - the name itself, plus all the groups it belongs to if $GL_BIG_CONFIG is
# set
# OR
# - (for repos) if the name itself doesn't exist in the config, a wildcard
# matching it, plus all the groups that wildcard belongs to (again if
# $GL_BIG_CONFIG is set)
# A name can normally appear (repo example) (user example)
# - directly (repo foo) (RW = bar)
# - (only for repos) as a direct wildcard (repo foo/.*)
# but if $GL_BIG_CONFIG is set, it can also appear:
# - indirectly (@g = foo; repo @g) (@ug = bar; RW = @ug))
# - (only for repos) as an indirect wildcard (@g = foo/.*; repo @g).
# things that may not be obvious from the above:
# - the wildcard stuff does not apply to username memberships
# - for repos, wildcard appearances are TOTALLY ignored if a non-wild
# appearance (direct or indirect) exists
sub get_memberships {
my $base = shift; # reponame or username
my $is_repo = shift; # some true value means a repo name has been passed
my $wild = '';
my (@ret, @ret_w); # maintain wild matches separately from non-wild
# direct
push @ret, $base if not $is_repo or exists $repos{$base};
if ($is_repo and $GL_WILDREPOS and not @ret) {
for my $i (sort keys %repos) {
if ($base =~ /^$i$/) {
die "$ABRT $base matches $wild AND $i\n" if $wild and $wild ne $i;
$wild = $i;
# direct wildcard
push @ret_w, $i;
}
}
}
if ($GL_BIG_CONFIG) {
for my $g (sort keys %groups) {
for my $i (sort keys %{ $groups{$g} }) {
if ($base eq $i) {
# indirect
push @ret, $g;
} elsif ($is_repo and $GL_WILDREPOS and not @ret and $base =~ /^$i$/) {
die "$ABRT $base matches $wild AND $i\n" if $wild and $wild ne $i;
$wild = $i;
# indirect wildcard
push @ret_w, $g;
}
}
}
}
# deal with returning user info first
unless ($is_repo) {
# add in group membership info sent in via second and subsequent
# arguments to gl-auth-command; be sure to prefix the "@" sign to each
# of them!
push @ret, map { s/^/@/; $_; } split(' ', $ENV{GL_GROUP_LIST}) if $ENV{GL_GROUP_LIST};
return (@ret);
}
# enforce the rule about ignoring all wildcard matches if a non-wild match
# exists while returning. (The @ret gating above does not adequately
# ensure this, it is only an optimisation).
#
# Also note that there is an extra return value when called for repos
# (compared to usernames)
return ((@ret ? '' : $wild), (@ret ? @ret : @ret_w));
}
# ----------------------------------------------------------------------------
# generic check access routine
# ----------------------------------------------------------------------------

View file

@ -28,6 +28,7 @@ our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMIND
# and these are set by gitolite.pm
our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT, $REPOPATT_PATT);
our %repos;
our %groups;
# the common setup module is in the same directory as this running program is
my $bindir = $0;
@ -55,6 +56,10 @@ my $repo_base_abs = $ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE
# start...
# ----------------------------------------------------------------------------
# no arguments given? default user is $USER (fedorahosted works like this,
# and it is harmless for others)
@ARGV = ($ENV{USER}) unless @ARGV;
# if the first argument is a "-s", this user is allowed to get a shell using
# this key
my $shell_allowed = 0;
@ -66,6 +71,10 @@ if ($ARGV[0] eq '-s') {
# first, fix the biggest gripe I have with gitosis, a 1-line change
my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere!
# if there are any more arguments, they're a list of group names that the user
# is a member of
$ENV{GL_GROUP_LIST} = join(" ", @ARGV) if @ARGV;
# ----------------------------------------------------------------------------
# logging, timestamp env vars
# ----------------------------------------------------------------------------
@ -184,7 +193,7 @@ $ENV{GL_REPO}=$repo;
# first level permissions check
# ----------------------------------------------------------------------------
my ($perm, $creator) = &repo_rights($repo);
my ($perm, $creator, $wild) = &repo_rights($repo);
if ($perm =~ /C/) {
# it was missing, and you have create perms
wrap_chdir("$repo_base_abs");

View file

@ -52,7 +52,7 @@ $Data::Dumper::Sortkeys = 1;
open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q');
# these are set by the "rc" file
our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $GL_WILDREPOS, $GL_GITCONFIG_KEYS, $GL_PACKAGE_HOOKS, $GL_SETPERMS_OVERRIDES_CONFIG);
our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $GL_WILDREPOS, $GL_GITCONFIG_KEYS, $GL_PACKAGE_HOOKS, $GL_SETPERMS_OVERRIDES_CONFIG, $GL_BIG_CONFIG, $GL_NO_DAEMON_NO_GITWEB);
# and these are set by gitolite.pm
our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN);
@ -173,6 +173,8 @@ sub parse_conf_file
# user or repo groups
if (/^(@\S+) = (.*)/)
{
die "$ABRT defining groups is not allowed inside fragments\n"
if $GL_BIG_CONFIG and $fragment ne 'master';
# store the members of each group as hash key. Keep track of when
# the group was *first* created by using $fragment as the *value*
do { $groups{$1}{$_} ||= $fragment } for ( expand_list( split(' ', $2) ) );
@ -181,10 +183,12 @@ sub parse_conf_file
# repo(s)
elsif (/^repo (.*)/)
{
# grab the list and expand any @stuff in it
# grab the list...
@repos = split ' ', $1;
unless (@repos == 1 and $repos[0] eq '@all') {
@repos = expand_list ( @repos );
# ...expand groups in the default case
@repos = expand_list ( @repos ) unless $GL_BIG_CONFIG;
# ...sanity check
for (@repos) {
die "$ABRT bad reponame $_\n"
if ($GL_WILDREPOS and $_ !~ $REPOPATT_PATT);
@ -214,7 +218,7 @@ sub parse_conf_file
# expand the user list, unless it is just "@all"
@users = expand_list ( @users )
unless (@users == 1 and $users[0] eq '@all');
unless ($GL_BIG_CONFIG or (@users == 1 and $users[0] eq '@all'));
do { die "$ABRT bad username $_\n" unless $_ =~ $USERNAME_PATT } for @users;
s/\bCREAT[EO]R\b/~\$creator/g for @users;
@ -237,6 +241,8 @@ sub parse_conf_file
# fragment is also called 'foo' (you're allowed to have a
# fragment that is only concerned with one repo)
( $fragment eq $repo ) or
# same thing in big-config-land; foo is just @foo now
( $GL_BIG_CONFIG and "\@$fragment" eq $repo ) or
# fragment is called "bar" and "@bar = foo" has been
# defined in the master config
( ($groups{"\@$fragment"}{$repo} || '') eq 'master' )
@ -370,6 +376,7 @@ my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]);
# much...
$dumped_data =~ s/'(?=[^']*\$(?:creator|readers|writers|gl_user))~*(.*?)'/"$1"/g;
print $compiled_fh $dumped_data;
print $compiled_fh Data::Dumper->Dump([\%groups], [qw(*groups)]) if $GL_BIG_CONFIG and %groups;
close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n";
# ----------------------------------------------------------------------------
@ -402,12 +409,20 @@ die "\n\t\t***** AAARGH! *****\n" .
# so if it was not already absolute, prefix $HOME.
my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
wrap_chdir("$repo_base_abs");
{
wrap_chdir("$repo_base_abs");
for my $repo (sort keys %repos) {
# autocreate repos. Start with the ones that are normal repos in %repos
my @repos = grep { $_ =~ $REPONAME_PATT and not /^@/ } sort keys %repos;
# then, for each repogroup, find the members of the group and add them in
map { push @repos, keys %{ $groups{$_} } } grep { /^@/ } keys %repos;
# weed out duplicates (the code in the loop below is disk activity!)
my %seen = map { $_ => 1 } @repos;
@repos = sort keys %seen;
for my $repo (sort @repos) {
next unless $repo =~ $REPONAME_PATT;
next if $repo =~ m(^EXTCMD/); # these are not real repos
next if $repo eq '@all';
next if $repo =~ m(^\@|EXTCMD/); # these are not real repos
unless (-d "$repo.git") {
print STDERR "creating $repo...\n";
new_repo($repo, "$GL_ADMINDIR/hooks/common");
@ -425,6 +440,7 @@ for my $repo (sort keys %repos) {
# override with the package hooks
ln_sf("$GL_PACKAGE_HOOKS/common", "*", "$repo.git/hooks") if $GL_PACKAGE_HOOKS;
}
}
}
# ----------------------------------------------------------------------------
@ -454,21 +470,24 @@ for my $repo (keys %repo_config) {
wrap_chdir("$repo_base_abs");
# daemons first...
for my $repo (sort keys %repos) {
unless ($GL_NO_DAEMON_NO_GITWEB) {
# daemons first...
for my $repo (sort keys %repos) {
next unless $repo =~ $REPONAME_PATT;
next if $repo =~ m(^\@|EXTCMD/); # these are not real repos
my $export_ok = "$repo.git/git-daemon-export-ok";
if ($repos{$repo}{'R'}{'daemon'}) {
system("touch $export_ok");
} else {
unlink($export_ok);
}
}
}
my %projlist = ();
# ...then gitwebs
for my $repo (sort keys %repos) {
my %projlist = ();
# ...then gitwebs
for my $repo (sort keys %repos) {
next unless $repo =~ $REPONAME_PATT;
next if $repo =~ m(^\@|EXTCMD/); # these are not real repos
my $desc_file = "$repo.git/description";
# note: having a description also counts as enabling gitweb
if ($repos{$repo}{'R'}{'gitweb'} or $desc{"$repo.git"}) {
@ -495,14 +514,15 @@ for my $repo (sort keys %repos) {
if (length($keys) == 0) {
system("git --git-dir=$repo.git config --remove-section gitweb 2>/dev/null");
}
}
}
# update the project list
my $projlist_fh = wrap_open( ">", $PROJECTS_LIST);
for my $proj (sort keys %projlist) {
# update the project list
my $projlist_fh = wrap_open( ">", $PROJECTS_LIST);
for my $proj (sort keys %projlist) {
print $projlist_fh "$proj\n";
}
close $projlist_fh;
}
close $projlist_fh;
# ----------------------------------------------------------------------------
# "compile" ssh authorized_keys
@ -560,10 +580,16 @@ for my $pubkey (`find . -type f`)
print $newkeys_fh $pubkey_content;
}
# lint check 3; a little more severe than the first two I guess...
for my $user (sort keys %user_list)
{
next if $user =~ /^(gitweb|daemon|\@all|~\$creator|\$readers|\$writers)$/ or $user_list{$user} eq 'has pubkey';
print STDERR "$WARN user $user in config, but has no pubkey!\n";
my @no_pubkey =
grep { $_ !~ /^(gitweb|daemon|\@.*|~\$creator|\$readers|\$writers)$/ }
grep { $user_list{$_} ne 'has pubkey' }
keys %user_list;
if (@no_pubkey > 10) {
print STDERR "$WARN You have " . scalar(@no_pubkey) . " users WITHOUT pubkeys...!\n";
} elsif (@no_pubkey) {
print STDERR "$WARN the following users have no pubkeys:\n", join(",", sort @no_pubkey), "\n";
}
}
print $newkeys_fh "# gitolite end\n";