diff options
Diffstat (limited to 'src/lib/Gitolite/Rc.pm')
-rw-r--r-- | src/lib/Gitolite/Rc.pm | 688 |
1 files changed, 688 insertions, 0 deletions
diff --git a/src/lib/Gitolite/Rc.pm b/src/lib/Gitolite/Rc.pm new file mode 100644 index 0000000..41996fb --- /dev/null +++ b/src/lib/Gitolite/Rc.pm @@ -0,0 +1,688 @@ +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', + # syslog 'facility': defaults to 'local0', uncomment if needed. For example: + # LOG_FACILITY => 'local4', + + # 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: |