Permalink
Fetching contributors…
Cannot retrieve contributors at this time
687 lines (511 sloc) 21.9 KB
package Gitolite::Rc;
# everything to do with 'rc'. Also defines some 'constants'
# ----------------------------------------------------------------------
@EXPORT = qw(
%rc
glrc
query_rc
version
greeting
trigger
_which
$REMOTE_COMMAND_PATT
$REF_OR_FILENAME_PATT
$REPONAME_PATT
$REPOPATT_PATT
$USERNAME_PATT
$UNSAFE_PATT
);
use Exporter 'import';
use Gitolite::Common;
# ----------------------------------------------------------------------
our %rc;
our $non_core;
# ----------------------------------------------------------------------
# pre-populate some important rc keys
# ----------------------------------------------------------------------
$rc{GL_BINDIR} = $ENV{GL_BINDIR};
$rc{GL_LIBDIR} = $ENV{GL_LIBDIR};
# these keys could be overridden by the rc file later
$rc{GL_REPO_BASE} = "$ENV{HOME}/repositories";
$rc{GL_ADMIN_BASE} = "$ENV{HOME}/.gitolite";
$rc{LOG_TEMPLATE} = "$ENV{HOME}/.gitolite/logs/gitolite-%y-%m.log";
# variables that should probably never be changed but someone will want to, I'll bet...
# ----------------------------------------------------------------------
#<<<
$REMOTE_COMMAND_PATT = qr(^[-0-9a-zA-Z._\@/+ :,\%=]*$);
$REF_OR_FILENAME_PATT = qr(^[0-9a-zA-Z][-0-9a-zA-Z._\@/+ :,]*$);
$REPONAME_PATT = qr(^\@?[0-9a-zA-Z][-0-9a-zA-Z._\@/+]*$);
$REPOPATT_PATT = qr(^\@?[[0-9a-zA-Z][-0-9a-zA-Z._\@/+\\^$|()[\]*?{},]*$);
$USERNAME_PATT = qr(^\@?[0-9a-zA-Z][-0-9a-zA-Z._\@+]*$);
$UNSAFE_PATT = qr([`~#\$\&()|;<>]);
#>>>
# ----------------------------------------------------------------------
# find the rc file and 'do' it
# ----------------------------------------------------------------------
my $current_data_version = "3.2";
my $rc = glrc('filename');
if ( -r $rc and -s $rc ) {
do $rc or die $@;
}
if ( defined($GL_ADMINDIR) ) {
say2 "";
say2 "FATAL: '$rc' seems to be for older gitolite; please see\nhttp://gitolite.com/gitolite/migr.html";
exit 1;
}
# let values specified in rc file override our internal ones
# ----------------------------------------------------------------------
@rc{ keys %RC } = values %RC;
# expand the non_core list into INPUT, PRE_GIT, etc using 'ENABLE' settings
non_core_expand() if $rc{ENABLE};
# add internal triggers
# ----------------------------------------------------------------------
# is the server/repo in a writable state (i.e., not down for maintenance etc)
unshift @{ $rc{ACCESS_1} }, 'Writable::access_1';
# (testing only) override the rc file silently
# ----------------------------------------------------------------------
# use an env var that is highly unlikely to appear in real life :)
do $ENV{G3T_RC} if exists $ENV{G3T_RC} and -r $ENV{G3T_RC};
# setup some perl/rc/env vars, plus umask
# ----------------------------------------------------------------------
umask ( $rc{UMASK} || 0077 );
unshift @INC, "$rc{LOCAL_CODE}/lib" if $rc{LOCAL_CODE};
$ENV{PATH} = "$ENV{GL_BINDIR}:$ENV{PATH}" unless $ENV{PATH} =~ /^$ENV{GL_BINDIR}:/;
{
$rc{GL_TID} = $ENV{GL_TID} ||= $$;
# TID: loosely, transaction ID. The first PID at the entry point passes
# it down to all its children so you can track each access, across all the
# various commands it spawns and actions it generates.
$rc{GL_LOGFILE} = $ENV{GL_LOGFILE} ||= gen_lfn( $rc{LOG_TEMPLATE} );
}
# these two are meant to help externally written commands (see
# src/commands/writable for an example)
$ENV{GL_REPO_BASE} = $rc{GL_REPO_BASE};
$ENV{GL_ADMIN_BASE} = $rc{GL_ADMIN_BASE};
# ----------------------------------------------------------------------
use strict;
use warnings;
# ----------------------------------------------------------------------
my $glrc_default_text = '';
{
local $/ = undef;
$glrc_default_text = <DATA>;
}
# ----------------------------------------------------------------------
sub non_core_expand {
my %enable;
for my $e ( @{ $rc{ENABLE} } ) {
my ( $name, $arg ) = split ' ', $e, 2;
# store args as the hash value for the name
$enable{$name} = $arg || '';
# for now, we pretend everything is a command, because commands
# are the only thing that the non_core list does not contain
$rc{COMMANDS}{$name} = $arg || 1;
}
# bring in additional non-core specs from the rc file, if given
if ( my $nc2 = $rc{NON_CORE} ) {
for ( $non_core, $nc2 ) {
# beat 'em into shape :)
s/#.*//g;
s/[ \t]+/ /g; s/^ //mg; s/ $//mg;
s/\n+/\n/g;
}
for ( split "\n", $nc2 ) {
next unless /\S/;
my ( $name, $where, $module, $before, $name2 ) = split ' ', $_;
if ( not $before ) {
$non_core .= "$name $where $module\n";
next;
}
die if $before ne 'before';
$non_core =~ s(^(?=$name2 $where( |$)))($name $where $module\n)m;
}
}
my @data = split "\n", $non_core || '';
for (@data) {
next if /^\s*(#|$)/;
my ( $name, $where, $module ) = split ' ', $_;
# if it appears here, it's not a command, so delete it. At the end of
# this loop, what's left in $rc{COMMANDS} will be those names in the
# enable list that do not appear in the non_core list.
delete $rc{COMMANDS}{$name};
next unless exists $enable{$name};
# module to call is name if specified as "."
$module = $name if $module eq ".";
# module to call is "name::pre_git" or such if specified as "::"
( $module = $name ) .= "::" . lc($where) if $module eq '::';
# append arguments, if supplied
$module .= " $enable{$name}" if $enable{$name};
push @{ $rc{$where} }, $module;
}
# finally, add in commands that were declared in the non-core list
map { /^(\S+)/; $rc{COMMANDS}{$1} = 1 } @{ $rc{COMMAND} };
}
# exported functions
# ----------------------------------------------------------------------
sub glrc {
my $cmd = shift;
if ( $cmd eq 'default-filename' ) {
return "$ENV{HOME}/.gitolite.rc";
} elsif ( $cmd eq 'default-text' ) {
return $glrc_default_text if $glrc_default_text;
_die "rc file default text not set; this should not happen!";
} elsif ( $cmd eq 'filename' ) {
# where is the rc file?
# search $HOME first
return "$ENV{HOME}/.gitolite.rc" if -f "$ENV{HOME}/.gitolite.rc";
return '';
} elsif ( $cmd eq 'current-data-version' ) {
return $current_data_version;
} else {
_die "unknown argument to glrc: '$cmd'";
}
}
my $all = 0;
my $dump = 0;
my $nonl = 0;
my $quiet = 0;
sub query_rc {
my @vars = args();
no strict 'refs';
if ($all) {
for my $e ( sort keys %rc ) {
print "$e=" . ( defined( $rc{$e} ) ? $rc{$e} : 'undef' ) . "\n";
}
exit 0;
}
if ($dump) {
require Data::Dumper;
$Data::Dumper::Sortkeys = 1;
print Data::Dumper::Dumper \%rc;
exit 0;
}
my $cv = \%rc; # current "value"
while (@vars) {
my $v = shift @vars;
# dig into the rc hash, using each var as a component
if ( not ref($cv) ) {
_warn "unused arguments...";
last;
} elsif ( ref($cv) eq 'HASH' ) {
$cv = $cv->{$v} || '';
} elsif ( ref($cv) eq 'ARRAY' ) {
$cv = $cv->[$v] || '';
} else {
_die "dont know what to do with " . ref($cv) . " item in the rc file";
}
}
# we've run out of arguments so $cv is what we have. If we're supposed to
# be quiet, we don't have to print anything so let's get that done first:
exit( $cv ? 0 : 1 ) if $quiet; # shell truth
# print values (notice we ignore the '-n' option if it's a ref)
if ( ref($cv) eq 'HASH' ) {
print join( "\n", sort keys %$cv ), "\n" if %$cv;
} elsif ( ref($cv) eq 'ARRAY' ) {
print join( "\n", @$cv ), "\n" if @$cv;
} else {
print $cv . ( $nonl ? '' : "\n" ) if $cv;
}
exit( $cv ? 0 : 1 ); # shell truth
}
sub version {
my $version = '';
$version = '(unknown)';
for ("$ENV{GL_BINDIR}/VERSION") {
$version = slurp($_) if -r $_;
}
chomp($version);
return $version;
}
sub greeting {
my $json = shift;
chomp( my $hn = `hostname -s 2>/dev/null || hostname` );
my $gv = substr( `git --version`, 12 );
my $gl_user = $ENV{GL_USER} || '';
$gl_user = " $gl_user" if $gl_user;
if ($json) {
$json->{GL_USER} = $ENV{GL_USER};
$json->{USER} = ( $ENV{USER} || "httpd" ) . "\@$hn";
$json->{gitolite_version} = version();
chomp( $json->{git_version} = $gv ); # this thing has a newline at the end
return;
}
# normal output
return "hello$gl_user, this is " . ( $ENV{USER} || "httpd" ) . "\@$hn running gitolite3 " . version() . " on git $gv\n";
}
sub trigger {
my $rc_section = shift;
# if arg-2 (now arg-1, due to the 'shift' above) exists, it is a repo
# name, so setup env from options
require Gitolite::Conf::Load;
Gitolite::Conf::Load->import('env_options');
env_options( $_[0] ) if $_[0];
if ( exists $rc{$rc_section} ) {
if ( ref( $rc{$rc_section} ) ne 'ARRAY' ) {
_die "'$rc_section' section in rc file is not a perl list";
} else {
for my $s ( @{ $rc{$rc_section} } ) {
my ( $pgm, @args ) = split ' ', $s;
if ( my ( $module, $sub ) = ( $pgm =~ /^(.*)::(\w+)$/ ) ) {
require Gitolite::Triggers;
trace( 2, 'trigger module', $module, $sub, @args, $rc_section, @_ );
Gitolite::Triggers::run( $module, $sub, @args, $rc_section, @_ );
} else {
$pgm = _which( "triggers/$pgm", 'x' );
_warn("skipped trigger '$s' (not found or not executable)"), next if not $pgm;
trace( 2, 'trigger command', $s );
_system( $pgm, @args, $rc_section, @_ ); # they better all return with 0 exit codes!
}
}
}
return;
}
trace( 3, "'$rc_section' not found in rc" );
}
sub _which {
# looks for a file in LOCAL_CODE or GL_BINDIR. Returns whichever exists
# (LOCAL_CODE preferred if defined) or 0 if not found.
my $file = shift;
my $mode = shift; # could be 'x' or 'r'
my @files = ("$rc{GL_BINDIR}/$file");
unshift @files, ("$rc{LOCAL_CODE}/$file") if $rc{LOCAL_CODE};
for my $f (@files) {
return $f if -x $f;
return $f if -r $f and $mode eq 'r';
}
return 0;
}
# ----------------------------------------------------------------------
=for args
Usage: gitolite query-rc -a
gitolite query-rc -d
gitolite query-rc [-n] [-q] rc-variable
-a print all variables and values (first level only)
-d dump the entire rc structure
-n do not append a newline if variable is scalar
-q exit code only (shell truth; 0 is success)
Query the rc hash. Second and subsequent arguments dig deeper into the hash.
The examples are for the default configuration; yours may be different.
Single values:
gitolite query-rc GL_ADMIN_BASE # prints "/home/git/.gitolite" or similar
gitolite query-rc UMASK # prints "63" (that's 0077 in decimal!)
Hashes:
gitolite query-rc COMMANDS
# prints "desc", "help", "info", "perms", "writable", one per line
gitolite query-rc COMMANDS help # prints 1
gitolite query-rc -q COMMANDS help # prints nothing; exit code is 0
gitolite query-rc COMMANDS fork # prints nothing; exit code is 1
Arrays (somewhat less useful):
gitolite query-rc POST_GIT # prints nothing; exit code is 0
gitolite query-rc POST_COMPILE # prints 4 lines
gitolite query-rc POST_COMPILE 0 # prints the first of those 4 lines
Explore:
gitolite query-rc -a
# prints all first level variables and values, one per line. Any that are
# listed as HASH or ARRAY can be explored further in subsequent commands.
gitolite query-rc -d # dump the entire rc structure
=cut
sub args {
my $help = 0;
require Getopt::Long;
Getopt::Long::GetOptions(
'all|a' => \$all,
'dump|d' => \$dump,
'nonl|n' => \$nonl,
'quiet|q' => \$quiet,
'help|h' => \$help,
) or usage();
_die("'-a' cannot be combined with other arguments or options; run with '-h' for usage") if $all and ( @ARGV or $dump or $nonl or $quiet );
usage() if not $all and not $dump and not @ARGV or $help;
return @ARGV;
}
# ----------------------------------------------------------------------
BEGIN {
$non_core = "
# No user-servicable parts inside. Warranty void if seal broken. Refer
# servicing to authorised service center only.
continuation-lines SYNTACTIC_SUGAR .
keysubdirs-as-groups SYNTACTIC_SUGAR .
macros SYNTACTIC_SUGAR .
refex-expr SYNTACTIC_SUGAR .
renice PRE_GIT .
Kindergarten INPUT ::
CpuTime INPUT ::
CpuTime POST_GIT ::
Shell INPUT ::
Alias INPUT ::
Motd INPUT ::
Motd PRE_GIT ::
Motd COMMAND motd
Mirroring INPUT ::
Mirroring PRE_GIT ::
Mirroring POST_GIT ::
refex-expr ACCESS_2 RefexExpr::access_2
expand-deny-messages ACCESS_1 .
expand-deny-messages ACCESS_2 .
RepoUmask PRE_GIT ::
RepoUmask POST_CREATE ::
partial-copy PRE_GIT .
upstream PRE_GIT .
no-create-on-read PRE_CREATE AutoCreate::deny_R
no-auto-create PRE_CREATE AutoCreate::deny_RW
ssh-authkeys-split POST_COMPILE post-compile/ssh-authkeys-split
ssh-authkeys POST_COMPILE post-compile/ssh-authkeys
Shell POST_COMPILE post-compile/ssh-authkeys-shell-users
set-default-roles POST_CREATE .
git-config POST_COMPILE post-compile/update-git-configs
git-config POST_CREATE post-compile/update-git-configs
create-with-reference POST_CREATE post-compile/create-with-reference
gitweb POST_CREATE post-compile/update-gitweb-access-list
gitweb POST_COMPILE post-compile/update-gitweb-access-list
cgit POST_COMPILE post-compile/update-description-file
daemon POST_CREATE post-compile/update-git-daemon-access-list
daemon POST_COMPILE post-compile/update-git-daemon-access-list
repo-specific-hooks POST_COMPILE .
repo-specific-hooks POST_CREATE .
";
}
1;
# ----------------------------------------------------------------------
__DATA__
# configuration variables for gitolite
# This file is in perl syntax. But you do NOT need to know perl to edit it --
# just mind the commas, use single quotes unless you know what you're doing,
# and make sure the brackets and braces stay matched up!
# (Tip: perl allows a comma after the last item in a list also!)
# HELP for commands can be had by running the command with "-h".
# HELP for all the other FEATURES can be found in the documentation (look for
# "list of non-core programs shipped with gitolite" in the master index) or
# directly in the corresponding source file.
%RC = (
# ------------------------------------------------------------------
# default umask gives you perms of '0700'; see the rc file docs for
# how/why you might change this
UMASK => 0077,
# look for "git-config" in the documentation
GIT_CONFIG_KEYS => '',
# comment out if you don't need all the extra detail in the logfile
LOG_EXTRA => 1,
# logging options
# 1. leave this section as is for 'normal' gitolite logging (default)
# 2. uncomment this line to log ONLY to syslog:
# LOG_DEST => 'syslog',
# 3. uncomment this line to log to syslog and the normal gitolite log:
# LOG_DEST => 'syslog,normal',
# 4. prefixing "repo-log," to any of the above will **also** log just the
# update records to "gl-log" in the bare repo directory:
# LOG_DEST => 'repo-log,normal',
# LOG_DEST => 'repo-log,syslog',
# LOG_DEST => 'repo-log,syslog,normal',
# roles. add more roles (like MANAGER, TESTER, ...) here.
# WARNING: if you make changes to this hash, you MUST run 'gitolite
# compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
ROLES => {
READERS => 1,
WRITERS => 1,
},
# enable caching (currently only Redis). PLEASE RTFM BEFORE USING!!!
# CACHE => 'Redis',
# ------------------------------------------------------------------
# rc variables used by various features
# the 'info' command prints this as additional info, if it is set
# SITE_INFO => 'Please see http://blahblah/gitolite for more help',
# the CpuTime feature uses these
# display user, system, and elapsed times to user after each git operation
# DISPLAY_CPU_TIME => 1,
# display a warning if total CPU times (u, s, cu, cs) crosses this limit
# CPU_TIME_WARN_LIMIT => 0.1,
# the Mirroring feature needs this
# HOSTNAME => "foo",
# TTL for redis cache; PLEASE SEE DOCUMENTATION BEFORE UNCOMMENTING!
# CACHE_TTL => 600,
# ------------------------------------------------------------------
# suggested locations for site-local gitolite code (see cust.html)
# this one is managed directly on the server
# LOCAL_CODE => "$ENV{HOME}/local",
# or you can use this, which lets you put everything in a subdirectory
# called "local" in your gitolite-admin repo. For a SECURITY WARNING
# on this, see http://gitolite.com/gitolite/non-core.html#pushcode
# LOCAL_CODE => "$rc{GL_ADMIN_BASE}/local",
# ------------------------------------------------------------------
# List of commands and features to enable
ENABLE => [
# COMMANDS
# These are the commands enabled by default
'help',
'desc',
'info',
'perms',
'writable',
# Uncomment or add new commands here.
# 'create',
# 'fork',
# 'mirror',
# 'readme',
# 'sskm',
# 'D',
# These FEATURES are enabled by default.
# essential (unless you're using smart-http mode)
'ssh-authkeys',
# creates git-config entries from gitolite.conf file entries like 'config foo.bar = baz'
'git-config',
# creates git-daemon-export-ok files; if you don't use git-daemon, comment this out
'daemon',
# creates projects.list file; if you don't use gitweb, comment this out
'gitweb',
# These FEATURES are disabled by default; uncomment to enable. If you
# need to add new ones, ask on the mailing list :-)
# user-visible behaviour
# prevent wild repos auto-create on fetch/clone
# 'no-create-on-read',
# no auto-create at all (don't forget to enable the 'create' command!)
# 'no-auto-create',
# access a repo by another (possibly legacy) name
# 'Alias',
# give some users direct shell access. See documentation in
# sts.html for details on the following two choices.
# "Shell $ENV{HOME}/.gitolite.shell-users",
# 'Shell alice bob',
# set default roles from lines like 'option default.roles-1 = ...', etc.
# 'set-default-roles',
# show more detailed messages on deny
# 'expand-deny-messages',
# show a message of the day
# 'Motd',
# system admin stuff
# enable mirroring (don't forget to set the HOSTNAME too!)
# 'Mirroring',
# allow people to submit pub files with more than one key in them
# 'ssh-authkeys-split',
# selective read control hack
# 'partial-copy',
# manage local, gitolite-controlled, copies of read-only upstream repos
# 'upstream',
# updates 'description' file instead of 'gitweb.description' config item
# 'cgit',
# allow repo-specific hooks to be added
# 'repo-specific-hooks',
# performance, logging, monitoring...
# be nice
# 'renice 10',
# log CPU times (user, system, cumulative user, cumulative system)
# 'CpuTime',
# syntactic_sugar for gitolite.conf and included files
# allow backslash-escaped continuation lines in gitolite.conf
# 'continuation-lines',
# create implicit user groups from directory names in keydir/
# 'keysubdirs-as-groups',
# allow simple line-oriented macros
# 'macros',
# Kindergarten mode
# disallow various things that sensible people shouldn't be doing anyway
# 'Kindergarten',
],
);
# ------------------------------------------------------------------------------
# per perl rules, this should be the last line in such a file:
1;
# Local variables:
# mode: perl
# End:
# vim: set syn=perl: