gitolite v2.0rc1 -- please see new developer-notes doc

This commit is contained in:
Sitaram Chamarty 2011-01-15 21:09:56 +05:30
parent d022d90031
commit 692552d146
34 changed files with 1224 additions and 863 deletions

View file

@ -1,5 +1,8 @@
<a name="start"></a>
***IMPORTANT NOTE: v2.0rc1 is out; all development will be on that now.
Please see new doc/developer-notes.mkd***
# gitolite
Gitolite is an access control layer on top of git, which allows access control

View file

@ -3,19 +3,21 @@
# PLEASE READ THE DOCUMENTATION BEFORE EDITING OR ASKING QUESTIONS
# ( http://github.com/sitaramc/gitolite/blob/pu/doc/gitolite.rc.mkd )
# this file is meant to be pulled into a perl program using "do" or "require".
# You do NOT need to know perl to edit the paths; it should be fairly
# self-explanatory and easy to maintain perl syntax :-)
# this file is in perl syntax. However, you do NOT need to know perl to edit
# it; it should be fairly self-explanatory and easy to maintain
# ------------------------------------------------------------------------------
# VARIABLES THAT SHOULD NOT BE TOUCHED AT ALL. EVER.
# DO NOT TOUCH THIS SECTION!
# ------------------------------------------------------------------------------
$GL_ADMINDIR=$ENV{HOME} . "/.gitolite";
$GL_CONF="$GL_ADMINDIR/conf/gitolite.conf";
$GL_KEYDIR="$GL_ADMINDIR/keydir";
$GL_CONF_COMPILED="$GL_ADMINDIR/conf/gitolite.conf-compiled.pm";
# DO NOT CHANGE THE NEXT TWO LINES UNLESS YOU REALLY KNOW WHAT YOU'RE DOING.
# DO NOT CHANGE THE NEXT FOUR LINES UNLESS YOU REALLY KNOW WHAT YOU'RE DOING.
# These variables are set automatically by the install method you choose.
# (PACKAGE MAINTAINERS: PLEASE READ doc/packaging.mkd)
# $GL_PACKAGE_CONF = "";
# $GL_PACKAGE_HOOKS = "";
@ -27,13 +29,14 @@ $PROJECTS_LIST = $ENV{HOME} . "/projects.list";
$REPO_UMASK = 0077;
# ------------------------------------------------------------------------------
# variables with an efficiency impact
# variables with an efficiency/performance impact
# ------------------------------------------------------------------------------
$GL_BIG_CONFIG = 0;
$GL_NO_DAEMON_NO_GITWEB = 0;
# $GL_NICE_VALUE = 0;
# ------------------------------------------------------------------------------
# VARIABLES WITH A SECURITY IMPACT. READ DOC WELL BEFORE CHANGING THESE.
# VARIABLES WITH A SECURITY IMPACT. READ DOCS BEFORE CHANGING THESE!
# http://github.com/sitaramc/gitolite/blob/pu/doc/gitolite.rc.mkd#_variables_with_a_security_impact
# ------------------------------------------------------------------------------
# $GL_ALL_READ_ALL = 0;
@ -59,19 +62,23 @@ $SVNSERVE = "";
# $ENV{GL_SLAVES} = 'gitolite@server2 gitolite@server3';
# PLEASE USE SINGLE QUOTES ABOVE, NOT DOUBLE QUOTES
$GL_WILDREPOS_PERM_CATS = "READERS WRITERS";
# $GL_SITE_INFO = "XYZ.COM DEVELOPERS: PLEASE SEE http://xyz.com/gitolite/help first";
# ------------------------------------------------------------------------------
# rarely changed variables
# ------------------------------------------------------------------------------
$GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y-%m.log";
# $GL_PERFLOGT="$GL_ADMINDIR/logs/perf-gitolite-%y-%m.log";
# $GL_SITE_INFO = "XYZ.COM DEVELOPERS: PLEASE SEE http://xyz.com/gitolite/help first";
# ------------------------------------------------------------------------------
# variables that should NOT be changed after the install step completes
# ------------------------------------------------------------------------------
$REPO_BASE="repositories";
# ------------------------------------------------------------------------------
# DO NOT TOUCH ANY THING AFTER THIS LINE
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# per perl rules, this should be the last line in such a file:
1;

View file

@ -2,6 +2,8 @@
. $(dirname $0)/adc.common-functions
[ -z "$GL_RC" ] && die "ENV GL_RC not set"
# get_rights_and_owner now also sets $repo; see comments in common functions
get_rights_and_owner $1; from=$repo
[ -z "$perm_read" ] && die "no read permissions on $from"
@ -17,7 +19,7 @@ git clone --bare -l $GL_REPO_BASE_ABS/$from.git $GL_REPO_BASE_ABS/$to.git
cd $GL_REPO_BASE_ABS/$to.git
echo $GL_USER > gl-creater
git config gitweb.owner "$GL_USER"
( cd $HOME;perl -le 'do ".gitolite.rc"; print $GL_WILDREPOS_DEFPERMS' ) |
( $GL_BINDIR/gl-query-rc GL_WILDREPOS_DEFPERMS ) |
SSH_ORIGINAL_COMMAND="setperms $to" $GL_BINDIR/gl-auth-command $GL_USER
cp -R $GL_REPO_BASE_ABS/$from.git/hooks/* $GL_REPO_BASE_ABS/$to.git/hooks

View file

@ -3,8 +3,12 @@
use strict;
use warnings;
die "ENV GL_RC not set\n" unless $ENV{GL_RC};
die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR};
unshift @INC, $ENV{GL_BINDIR};
require gitolite or die "parse gitolite.pm failed\n";
gitolite->import;
# get the repo name
my $repo = shift;
@ -15,7 +19,7 @@ $repo =~ s/\.git$//;
# to do a "level 1" check (repo level -- not branch level), do this:
my ($perm, $creator) = &check_access($repo);
my ($perm, $creator) = check_access($repo);
# you can pass in any repo name you wish instead of the active repo
# the first return value looks like one of these, so you can just check for
@ -30,7 +34,7 @@ my ($perm, $creator) = &check_access($repo);
# to do a "level 2" check (branches), do something like this
my $ret = &check_access($repo, 'refs/heads/foo', 'W', 1);
my $ret = check_access($repo, 'refs/heads/foo', 'W', 1);
# the 2nd argument must be a *full* refname (i.e., not "master", but
# "refs/heads/master"). The 3rd argument is one of W, +, C, or D. The 4th
# argument should be any non-false perl value, like 1.

View file

@ -3,6 +3,9 @@
use strict;
use warnings;
die "ENV GL_RC not set\n" unless $ENV{GL_RC};
die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR};
# - show fake "reflog" from gitolite server
# - recover deleted branches
# - recover from bad force pushes
@ -42,8 +45,9 @@ $limit ||= 10;
unshift @INC, $ENV{GL_BINDIR};
require gitolite or die "parse gitolite.pm failed\n";
gitolite->import;
my ($perm, $creator, $wild) = &repo_rights($repo);
my ($perm, $creator, $wild) = repo_rights($repo);
die "you don't have read access to $repo\n" unless $perm =~ /R/;
my @logfiles = sort glob("$ENV{GL_ADMINDIR}/logs/*");
@ -93,5 +97,5 @@ if ( $cmd eq 'recover' ) {
$newsha2 = '' if $newsha =~ /^0+$/;
system("git", "update-ref", $ref, $oldsha, $newsha2) and
die "repo $repo, update-ref $ref $oldsha $newsha failed...\n";
&log_it("", "+\t$newsha\t$oldsha\t$repo\t$ref");
log_it("", "+\t$newsha\t$oldsha\t$repo\t$ref");
}

View file

@ -2,6 +2,8 @@
. $(dirname $0)/adc.common-functions
[ -z "$GL_RC" ] && die "ENV GL_RC not set"
# options settable in adc.common-functions are
# ARE_YOU_SURE -- prompts "are you sure?"
# USE_LOCK_UNLOCK -- allows delete only if repo is "unlock"ed
@ -32,6 +34,6 @@ rm -rf $repo.git
echo "$repo is now GONE!"
cd $HOME
PROJECTS_LIST=$(perl -e 'do ".gitolite.rc"; print $PROJECTS_LIST')
PROJECTS_LIST=$($GL_BINDIR/gl-query-rc PROJECTS_LIST)
export repo
perl -ni -e 'print unless /^\Q$ENV{repo}.git\E$/' $PROJECTS_LIST

View file

@ -20,10 +20,10 @@ $doc =~ s/^<a name="_.*"><\/a>\n\n//mg;
$doc =~ s/^<a name="AUTO_.*"><\/a>\n\n//mg;
my @toc = $doc =~ /^###+ .*/mg;
$doc =~ s/^(###+) (.*)/"<a name=\"_" . &make_anchor($2) . "\"><\/a>\n\n$1 $2"/mge;
$doc =~ s/^(###+) (.*)/"<a name=\"_" . make_anchor($2) . "\"><\/a>\n\n$1 $2"/mge;
for (@toc) {
s/^(###+) (.*)/' ' x (length($1)-3) . ' * <a href="#_' . &make_anchor($2) . "\">$2<\/a>"/e;
s/^(###+) (.*)/' ' x (length($1)-3) . ' * <a href="#_' . make_anchor($2) . "\">$2<\/a>"/e;
}
my $toc = "In this document:\n\n";

View file

@ -6,29 +6,36 @@
# HOME of the gitolite user
my $gl_home = "/home/git";
# environment variables needed by gitolite.pm
# the following variables are needed by gitolite; please edit before using
# this should normally not be anything else
$ENV{GL_RC} = "$gl_home/.gitolite.rc";
# this can have different values depending on how you installed.
# If you installed using the 'from-client' method it will be this:
$ENV{GL_BINDIR} = "$gl_home/.gitolite/src";
# if you used RPM/DEB or "root" methods it **might** be this:
$ENV{GL_BINDIR} = "/usr/local/bin";
# if you used the "non-root" method it **might** be this:
$ENV{GL_BINDIR} = "$gl_home/bin";
# If in doubt take a look at ~/.ssh/authorized_keys; at least one of the lines
# might contain something like:
# command="/home/git/.gitolite/src/gl-auth-command
# and you should use whatever directory the gl-auth-command is in (in this
# example /home/git/.gitolite.src)
# finally the user name
$ENV{GL_USER} = $cgi->remote_user || "gitweb";
# variables from the RC file
our ($REPO_BASE, $GL_ADMINDIR);
# set HOME temporarily for RC parsing
my $orig_home = $ENV{HOME};
$ENV{HOME} = $gl_home;
do $ENV{GL_RC}
or die_error(500, "Failed to parse $ENV{GL_RC}: " . ($! or $@));
$ENV{HOME} = $orig_home;
# now get gitolite stuff in...
use lib $ENV{GL_BINDIR};
use gitolite_rc;
use gitolite;
# set project root etc. absolute paths
$ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$gl_home/$REPO_BASE" );
$projects_list = $projectroot = $ENV{GL_REPO_BASE_ABS};
# load gitolite helper routines
unshift @INC, "$GL_ADMINDIR/src";
require gitolite
or die_error(500, "Failed to parse gitolite.pm: " . ($! or $@));
$export_auth_hook = sub {
my $repo = shift;
# gitweb passes us the full repo path; so we strip the beginning

View file

@ -119,7 +119,7 @@ Finally, you can call gitolite to query ownership and permissions for the
current user (which may not necessarily be the owner). This is done loosely
as follows (don't use this exact code yet though):
perl -I$HOME/.gitolite/src -Mgitolite -e "cli_repo_rights('reponame')"
perl -I$GL_BINDIR -Mgitolite -e "cli_repo_rights('reponame')"
which will print two space-separated words, something like `_____R__W u1` or
maybe `____@R_@W <gitolite>`. (The former is the response for a wildcard repo

213
doc/developer-notes.mkd Normal file
View file

@ -0,0 +1,213 @@
# developer/patch maintainer notes
In this document:
* <a href="#_current_development_version">current development version</a>
* <a href="#_testing_status_of_gitolite_v2_0rc1">testing status of gitolite v2.0rc1</a>
* <a href="#_migration_to_gitolite_v2_x">migration to gitolite v2.x</a>
* <a href="#_general_stuff">general stuff</a>
* <a href="#_the_rc_file">the rc file</a>
* <a href="#_modules">modules</a>
* <a href="#_that_bindir_thing">that 'bindir' thing</a>
* <a href="#_from_perl">from perl</a>
* <a href="#_from_shell">from shell</a>
* <a href="#_OUTLIER_">OUTLIER!</a>
* <a href="#_special_types_of_setups">special types of setups</a>
* <a href="#_Fedora">Fedora</a>
----
<a name="_current_development_version"></a>
### current development version
The current gitolite development version is v2.0rc1. Unless there is a
serious security problem, *or* one of my large users [i.e., anyone whose name
is in doc/who-uses-it.mkd (grin!)] needs it, all future changes will now
happen here.
The commit looks *huge*, but it's mostly just large chunks of code moving
around; there's not a whole lot of new code. However, I do apologise if
anyone has their local changes conflicted when merging or rebasing against
this version, and I promise to help as much as I can.
<a name="_testing_status_of_gitolite_v2_0rc1"></a>
#### testing status of gitolite v2.0rc1
Pretty much all the major features have been properly tested using the test
suite. The following exceptions exist:
* basic, manual, testing only
* most admin defined commands
* not yet tested
* smart http
* mirroring
* mob branches
* things which I have no easy way to test
* controlling gitweb authentication using gitolite (as described [here][gw])
* SVN passthru
<a name="_migration_to_gitolite_v2_x"></a>
### migration to gitolite v2.x
In general, the procedure for migrating described in the install document
should suffice. Even the rc file hasn't really changed much from the latest
versions in v1.x, except that if you add a new variable to it you must also
add it to the @EXPORT list in `src/gitolite_rc.pm`.
<a name="_general_stuff"></a>
### general stuff
* all scripts and libraries must be in the same directory. However, RPM/DEB
packagers can put the libraries where they want, as long as they can be
found in perl's default `@INC`.
* gl-auth-command **requires** an actual `~/.gitolite.rc` (except if your
initials are "JK" or "DG", in which case `/etc/gitolite/gitolite.rc` also
works!) It knows how to look around and set env vars etc correctly
* all programs except gl-auth-command **require** the environment variables
`GL_RC` and `GL_BINDIR` set properly. Your best bet is to run them *via*
gl-auth-command, like so:
path/to/gl-auth-command -e other_program other_program_arguments
In any case none of these programs are meant to be run manually -- pretty
much all of them are run via gl-auth-command or from something that was
forked from it so the variables *will* exist during normal operation.
<a name="_the_rc_file"></a>
### the rc file
The 'rc' file has one major change from v1: any new values in the rc file need
to be added to the @EXPORT list in `src/gitolite_rc.pm`.
<a name="_modules"></a>
### modules
There are 3 "modules" (`gitolite_rc`, `gitolite_env`, and `gitolite` itself).
Their purposes should be fairly obvious.
<a name="_that_bindir_thing"></a>
### that 'bindir' thing
The importance of `GL_BINDIR` is that the command= argument in
`~/.ssh/authorized_keys` must be a full path, ideally, and the compile script
gets this from `GL_BINDIR`.
<a name="_from_perl"></a>
#### from perl
* for frequently run perl programs, I prefer my method
* gl-auth-command -- this is invoked with a full path
* gl-time -- same as above
* "their" ideal is "FindBin". I will use it only on manually or
infrequently run programs
* gl-setup-authkeys (external shim to compile keys separately from PTA)
<a name="_from_shell"></a>
#### from shell
* a perl program called gl-query-rc finds its own BINDIR (using my perl
method, not FindBin). This is suitable for calling from shell scripts
as `${0%/*}/gl-query-rc GL_BINDIR`
* gl-mirror-shell (frequent use)
* gl-setup
* gl-tool
<a name="_OUTLIER_"></a>
#### OUTLIER!
* gl-dont-panic is an outlier. For some silly reason I have the notion that
even if it runs from /tmp it should get the right values, so it is the
only one that interrogates `~/.ssh/authorized_keys` to get the actual
BINDIR in use!
<a name="_special_types_of_setups"></a>
### special types of setups
<a name="_Fedora"></a>
#### Fedora
Fedora has a very special setup, as follows:
* each user has his own userid and login
* his/her ~/.ssh/authkeys file (containing only his/her key) has a
"command=" clause invoking gl-auth-command
* trusted users have "gl-auth-command -s" meaning they can get a shell if
they want to
* actual git repos are under "git" (or some such), and include the chmod g+s
(git init --shared) unix perms tricks for shared access
* but since they're coming through gl-auth, branch-level acls are in effect
* the gitolite config file is generated from some database and compiled (all
via cron)
* the keydir/ is empty; in fact they probably don't use the admin repo at
all, AFAIK
The most important implication of this setup is that **the RC file is no
longer is `$HOME` of the 'git' user**. They keep it in
`/etc/gitolite/gitolite.rc`. This means that a properly setup rc file must
already be present in `/etc/gitolite/gitolite.rc` before doing any such
installs.
----
# **Why v2?**
I went onto `#perl` to ask some question about setpriority() and got yelled at
for writing "horrible code". And that was one of the kinder comments; my
rather fragile ego is trying to forget the rest ;-)
They also gave me a link to a PDF book, "Modern Perl" by 'chromatic'. Nice
book; one of the first things you learn from it is that you should not go to
`#perl` for general help.
Anyway, the summary of the collective angst of `#perl` (well 2 people anyway)
was: use Getopt::Long, FindBin, 'use lib', a library for HTTP stuff, stop
prefixing subs with '&', and get rid of the huge number of 'our' declarations.
That last item is the only one I totally agree with, because it was on my long
term todo list anyway. And 'use lib' sorta goes with it, so that's fine too.
And as soon as I found that vim colors the sub names differently if you take
out the '&' I decided I'd do that too :-) [But honestly, if `&sub` is so bad
shouldn't "man perlsub" at least say something negative about it, other than
"disables prototype checking", which doesn't matter here since I'm not using
prototypes?]
As for the rest, FindBin brings in a good 1000+ lines for something that I do
in a line or two (since I don't care about all the pathological edge cases).
Getopt::Long is 2649 lines to replace the code below [note that there *is*
only one possible option to this command, and it is *never* run manually
either, so I don't need any fancy features]:
my $shell_allowed = 0;
if (@ARGV and $ARGV[0] eq '-s') {
$shell_allowed = 1;
shift;
}
Apparently TMTOWTDI has given way to TOOWTDI.
Anyway, I spent a few hours refactoring it. And I do thank them for pushing
me to stop being lazy on the "our" business.
[gw]: https://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#_easier_to_link_gitweb_authorisation_with_gitolite

View file

@ -61,7 +61,7 @@ things happen if you change them.
<a name="_variables_with_an_efficiency_impact"></a>
### variables with an efficiency impact
### variables with an efficiency/performance impact
* `$GL_BIG_CONFIG`, boolean, default 0
@ -79,6 +79,12 @@ things happen if you change them.
blocks repo config settings. Please read [doc/big-config.mkd][bc] for
more details.
* `$GL_NICE_VALUE`, boolean, default undef
The nice value to run under. Applicable only if it is greater than 0.
Please do NOT set it if your bits/resource.h does not define PRIO_PROCESS
is 0. For Linux this is true...
<a name="_variables_with_a_security_impact"></a>
### variables with a security impact

View file

@ -113,17 +113,21 @@ bar.pub and baz.pub, etc.
#### setup the mirror-shell on each server
XXX review this document after testing mirroring...
If you installed gitolite using the from client method, run the following:
# on foo
export GL_ADMINDIR=` cd $HOME;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR'`
export GL_BINDIR=$HOME/.gitolite/src
cat bar.pub baz.pub |
sed -e 's,^,command="'$GL_ADMINDIR'/src/gl-mirror-shell" ,' >> ~/.ssh/authorized_keys
sed -e 's,^,command="'$GL_BINDIR'/gl-mirror-shell" ,' >> ~/.ssh/authorized_keys
If you installed using any of the other 3 methods do this:
# on foo
export GL_BINDIR=`gl-query-rc GL_BINDIR`
cat bar.pub baz.pub |
sed -e 's,^,command="'$(which gl-mirror-shell)'" ,' >> ~/.ssh/authorized_keys
sed -e 's,^,command="'$GL_BINDIR'/gl-mirror-shell" ,' >> ~/.ssh/authorized_keys
Also do the same thing on the other machines.

View file

@ -36,7 +36,9 @@ clone's `hooks/common` directory, containing the following code:
#!/bin/bash
GL_ADMINDIR=` cd;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR'`
[ -z "$GL_RC" ] && { echo "ENV GL_RC not set"; exit 1; }
GL_ADMINDIR=`$GL_BINDIR/gl-query-rc GL_ADMINDIR`
cp $GL_ADMINDIR/local/gitolite.rc $HOME/.gitolite.rc
cp -a $GL_ADMINDIR/local/hooks/* $GL_ADMINDIR/hooks/common

View file

@ -6,50 +6,32 @@ use warnings;
# === update ===
# this is gitolite's update hook
# part of the gitolite (GL) suite
# how run: via git, being copied as .git/hooks/update in every repo
# when: every push
# input:
# - see man githooks for STDIN
# - uses the compiled config file to get permissions info
# output: based on permissions etc., exit 0 or 1
# security:
# - none
# robustness:
# other notes:
# ----------------------------------------------------------------------------
# common definitions
# find the rc file, then pull the libraries
# ----------------------------------------------------------------------------
our ($GL_CONF_COMPILED, $UPDATE_CHAINS_TO, $GL_PERFLOGT);
our %repos;
BEGIN {
die "ENV GL_RC not set\n" unless $ENV{GL_RC};
die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR};
}
# people with shell access should be allowed to bypass the update hook, simply
# by setting an env var that the ssh "front door" will never set
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};
unshift @INC, $ENV{GL_BINDIR};
require gitolite or die "parse gitolite.pm failed\n";
my ($perm, $creator, $wild) = &repo_rights($ENV{GL_REPO});
my $reported_repo = $ENV{GL_REPO} . ( $ENV{GL_REPOPATT} ? " ($ENV{GL_REPOPATT})" : "" );
use lib $ENV{GL_BINDIR};
use gitolite_rc;
use gitolite qw(:DEFAULT %repos);
# ----------------------------------------------------------------------------
# start...
# ----------------------------------------------------------------------------
my @saved_ARGV = @ARGV; # for chaining to another update hook at the end
# people with shell access should be allowed to bypass the update hook, simply
# by setting an env var that the ssh "front door" will never set
exit 0 if exists $ENV{GL_BYPASS_UPDATE_HOOK};
my $ref = shift;
my $oldsha = shift;
my $newsha = shift;
my ($perm, $creator, $wild) = repo_rights($ENV{GL_REPO});
my $reported_repo = $ENV{GL_REPO} . ( $wild ? " ($wild)" : "" );
# arguments are as supplied to an update hook by git; man githooks
my ($ref, $oldsha, $newsha) = @ARGV;
my $merge_base = '0' x 40;
# compute a merge-base if both SHAs are non-0, else leave it as '0'x40
# (i.e., for branch create or delete, merge_base == '0'x40)
@ -97,21 +79,21 @@ if (exists $repos{$ENV{GL_REPO}}{NAME_LIMITS}) {
push @refs, map { chomp; s/^/NAME\//; $_; } `git diff --name-only $oldtree $newtree`;
}
# and in this version, we have many "refs" to check. The one we print in the
# 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)
# we potentially have many "refs" to check. The one we print in the 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), $att_acc);
&check_ref (\@allowed_refs, $ENV{GL_REPO}, $_ , $att_acc) for @refs;
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("", "$att_acc\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) .
log_it("", "$att_acc\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) .
"\t$reported_repo\t$ref\t$log_refex");
# now chain to the local admin defined update hook, if present
$UPDATE_CHAINS_TO ||= 'hooks/update.secondary';
exec $UPDATE_CHAINS_TO, @saved_ARGV
exec $UPDATE_CHAINS_TO, @ARGV
if -f $UPDATE_CHAINS_TO or -l $UPDATE_CHAINS_TO;
exit 0;

View file

@ -1,5 +1,9 @@
#!/bin/sh
[ -z "$GL_RC" ] && die "ENV GL_RC not set"
[ -z "$GL_BINDIR" ] && die "ENV GL_BINDIR not set"
[ -z "$GL_ADMINDIR" ] && die "ENV GL_ADMINDIR not set"
# ensure that the admin is not sneaking in src/ and hooks/ :)
GIT_WORK_TREE=$GL_ADMINDIR git ls-tree --name-only master |
perl -lne 'exit 1 if /^(src|hooks)$/' || {
@ -19,7 +23,7 @@ $GL_BINDIR/gl-compile-conf
cd $od
ADMIN_POST_UPDATE_CHAINS_TO=` cd $HOME;perl -e 'do ".gitolite.rc"; print $ADMIN_POST_UPDATE_CHAINS_TO'`
ADMIN_POST_UPDATE_CHAINS_TO=`$GL_BINDIR/gl-query-rc ADMIN_POST_UPDATE_CHAINS_TO`
[ -n "$ADMIN_POST_UPDATE_CHAINS_TO" ] || ADMIN_POST_UPDATE_CHAINS_TO=hooks/post-update.secondary
if [ -f $ADMIN_POST_UPDATE_CHAINS_TO ] || [ -L $ADMIN_POST_UPDATE_CHAINS_TO ]

View file

@ -1,56 +1,71 @@
# lots of common routines
package gitolite;
use Exporter 'import';
@EXPORT = qw(
can_read
check_access
check_ref
check_repo_write_enabled
cli_repo_rights
dbg
list_phy_repos
ln_sf
log_it
new_repo
new_wild_repo
repo_rights
run_custom_command
setup_authkeys
setup_daemon_access
setup_git_configs
setup_gitweb_access
shell_out
special_cmd
try_adc
wrap_chdir
wrap_open
wrap_print
);
@EXPORT_OK = qw(
%repos
%groups
%git_configs
%split_conf
);
use strict;
use warnings;
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)
# warning: preceding para requires 4th attribute of a programmer after
# laziness, impatience, and hubris: sense of humour :-)
# WARNING
# -------
# the name of this file will change as soon as its function/feature set
# stabilises enough ;-)
# right now all it does is
# - define a function that tells you where to find the rc file
# - define a function that creates a new repo and give it our update hook
# ----------------------------------------------------------------------------
# common definitions
# find the rc file, then pull the libraries
# ----------------------------------------------------------------------------
our $ABRT = "\n\t\t***** ABORTING *****\n ";
our $WARN = "\n\t\t***** WARNING *****\n ";
BEGIN {
die "ENV GL_RC not set\n" unless $ENV{GL_RC};
die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR};
}
# commands we're expecting
our $R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/;
our $W_COMMANDS=qr/^git[ -]receive-pack$/;
use lib $ENV{GL_BINDIR};
use gitolite_rc;
# note that REPONAME_PATT allows "/", while USERNAME_PATT does not
# also, the reason REPONAME_PATT is a superset of USERNAME_PATT is (duh!)
# because in this version, a repo can have "CREATOR" in the name (see docs)
our $REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$); # very simple pattern
our $USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$); # very simple pattern
# same as REPONAME, but used for wildcard repos, allows some common regex metas
our $REPOPATT_PATT=qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$);
# ADC commands and arguments must match this pattern
our $ADC_CMD_ARGS_PATT=qr(^[0-9a-zA-Z._\@/+:-]*$);
# ----------------------------------------------------------------------------
# the big data structures we care about
# ----------------------------------------------------------------------------
# these come from the RC file
our ($REPO_UMASK, $GL_WILDREPOS, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS, $REPO_BASE, $GL_CONF_COMPILED, $GL_BIG_CONFIG, $GL_PERFLOGT, $PROJECTS_LIST, $GL_ALL_INCLUDES_SPECIAL, $GL_SITE_INFO, $GL_GET_MEMBERSHIPS_PGM, $GL_WILDREPOS_PERM_CATS);
our %repos;
our %groups;
our %git_configs;
our %split_conf;;
our %split_conf;
our $data_version;
our $current_data_version = '1.7';
# the following are read in from individual repo's gl-conf files, if present
our %one_repo;
our %one_git_config;
our %one_repo; # corresponds to what goes into %repos
our %one_git_config; # ditto for %git_configs
# ----------------------------------------------------------------------------
# convenience subs
@ -94,45 +109,12 @@ sub add_del_line {
}
sub dbg {
use Data::Dumper;
for my $i (@_) {
print STDERR "DBG: $i\n";
print STDERR "DBG: " . Dumper($i);
}
}
my $http_headers_printed = 0;
sub print_http_headers {
my($code, $text) = @_;
return if $http_headers_printed++;
$code ||= 200;
$text ||= "OK - gitolite";
$|++;
print "Status: $code $text\r\n";
print "Expires: Fri, 01 Jan 1980 00:00:00 GMT\r\n";
print "Pragma: no-cache\r\n";
print "Cache-Control: no-cache, max-age=0, must-revalidate\r\n";
print "\r\n";
}
sub get_logfilename {
# this sub has a wee little side-effect; it sets $ENV{GL_TS}
my($template) = shift;
my ($s, $min, $h, $d, $m, $y) = (localtime)[0..5];
$y += 1900; $m++; # usual adjustments
for ($s, $min, $h, $d, $m) {
$_ = "0$_" if $_ < 10;
}
$ENV{GL_TS} = "$y-$m-$d.$h:$min:$s";
# substitute template parameters and set the logfile name
$template =~ s/%y/$y/g;
$template =~ s/%m/$m/g;
$template =~ s/%d/$d/g;
return ($template);
}
sub log_it {
my ($ip, $logmsg);
open my $log_fh, ">>", $ENV{GL_LOG} or die "open log failed: $!\n";
@ -147,33 +129,6 @@ sub log_it {
close $log_fh or die "close log failed: $!\n";
}
# check one ref
sub check_ref {
# normally, the $ref will be whatever ref the commit is trying to update
# (like refs/heads/master or whatever). At least one of the refexes that
# pertain to this user must match this ref **and** the corresponding
# permission must also match the action (W or +) being attempted. If none
# of them match, the access is denied.
# Notice that the function DIES unless a non-false 5th argument is present
my ($allowed_refs, $repo, $ref, $perm, $dry_run) = @_;
my @allowed_refs = sort { $a->[0] <=> $b->[0] } @{$allowed_refs};
for my $ar (@allowed_refs) {
my $refex = $ar->[1];
# refex? sure -- a regex to match a ref against :)
next unless $ref =~ /^$refex/;
return "DENIED by $refex" if $ar->[2] eq '-' and $dry_run;
die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->[2] eq '-';
# as far as *this* ref is concerned we're ok
return $refex if ($ar->[2] =~ /\Q$perm/);
}
return "DENIED by fallthru" if $dry_run;
die "$perm $ref $repo $ENV{GL_USER} DENIED by fallthru\n";
}
# ln -sf :-)
sub ln_sf
{
@ -200,71 +155,35 @@ sub list_phy_repos
return @phy_repos;
}
# ----------------------------------------------------------------------------
# birth and death registration ;-)
# serious logic subs (as opposed to just "convenience" subs)
# ----------------------------------------------------------------------------
# background
# check one ref
sub check_ref {
# till now, the rc file was in one fixed place: .gitolite.rc in $HOME of the
# user hosting the gitolite repos. This was fine, because gitolite is all
# about empowering non-root users :-)
# normally, the $ref will be whatever ref the commit is trying to update
# (like refs/heads/master or whatever). At least one of the refexes that
# pertain to this user must match this ref **and** the corresponding
# permission must also match the action (W/+, or C/D if used) being
# attempted. If none of them match, the access is denied.
# but in smart http mode, running under "apache", you should actually use
# $GITOLITE_HTTP_HOME instead of $HOME (in fact $HOME may not even be
# defined). However, the dependency on $HOME is so pervasive that we'd best
# just set it here and be done. We also set $ENV{GL_RC} to point to the rc
# file
# NOTE: the function DIES when access is denied, unless arg 5 is true
# every gitolite program ends up calling this anyway, so that's birth
my ($allowed_refs, $repo, $ref, $perm, $dry_run) = @_;
my @allowed_refs = sort { $a->[0] <=> $b->[0] } @{$allowed_refs};
for my $ar (@allowed_refs) {
my $refex = $ar->[1];
# refex? sure -- a regex to match a ref against :)
next unless $ref =~ /^$refex/;
return "DENIED by $refex" if $ar->[2] eq '-' and $dry_run;
die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->[2] eq '-';
# the second thing we need to do is handle death a little better. A plain
# "die" was fine for ssh but http has all that extra gunk it needs. So we
# need to, in effect, create a "death handler".
# the name of the sub, however, is a holdover from when that was the sole
# purpose. I suck at function names anyway...
sub where_is_rc
{
die "I need either HOME or GITOLITE_HTTP_HOME env vars set\n" unless $ENV{GITOLITE_HTTP_HOME} or $ENV{HOME};
if ($ENV{GITOLITE_HTTP_HOME}) {
# smart http mode; GITOLITE_HTTP_HOME becomes our HOME
$ENV{HOME} = $ENV{GITOLITE_HTTP_HOME};
$SIG{__DIE__} = sub {
my $service = ($ENV{SSH_ORIGINAL_COMMAND} =~ /git-receive-pack/ ? 'git-receive-pack' : 'git-upload-pack');
my $message = shift; chomp($message);
print STDERR "$message\n";
# format the service response, then the message. With initial
# help from Ilari and then a more detailed email from Shawn...
$service = "# service=$service\n"; $message = "ERR $message\n";
$service = sprintf("%04X", length($service)+4) . "$service"; # no CRLF on this one
$message = sprintf("%04X", length($message)+4) . "$message";
&print_http_headers();
print $service;
print "0000"; # flush-pkt, apparently
print $message;
print STDERR $service;
print STDERR $message;
exit 0; # if it's ok for die_webcgi in git.git/http-backend.c, it's ok for me ;-)
}
}
return if $ENV{GL_RC};
# Fedora doesn't actually have a "hosting user" at all (yeah -- bet you
# didn't know gitolite was *that* flexible!), so there's no fixed $HOME,
# and they prefer to keep their RC file in /etc/gitolite.
for my $glrc ( $ENV{HOME} . "/.gitolite.rc", "/etc/gitolite/gitolite.rc" ) {
if (-f $glrc) {
$ENV{GL_RC} = $glrc;
last;
}
# as far as *this* ref is concerned we're ok
return $refex if ($ar->[2] =~ /\Q$perm/);
}
return "DENIED by fallthru" if $dry_run;
die "$perm $ref $repo $ENV{GL_USER} DENIED by fallthru\n";
}
# ----------------------------------------------------------------------------
@ -302,8 +221,21 @@ sub new_repo
system("env GL_REPO='$repo' hooks/gl-post-init") if -x "hooks/gl-post-init";
}
sub new_wild_repo {
my ($repo, $user) = @_;
wrap_chdir("$ENV{GL_REPO_BASE_ABS}");
new_repo($repo, "$GL_ADMINDIR/hooks/common", $user);
# note pwd is now the bare "repo.git"; new_repo does that...
wrap_print("gl-perms", "$GL_WILDREPOS_DEFPERMS\n") if $GL_WILDREPOS_DEFPERMS;
setup_git_configs($repo, \%git_configs);
setup_daemon_access($repo);
add_del_line ("$repo.git", $PROJECTS_LIST, setup_gitweb_access($repo, '', ''));
wrap_chdir($ENV{HOME});
}
# ----------------------------------------------------------------------------
# metaphysics (like, "is there a god?", "who created me?", etc)
# wild_repo_rights
# ----------------------------------------------------------------------------
{
@ -393,7 +325,7 @@ sub get_set_perms
my($repo, $verb, $user) = @_;
# set default categories
$GL_WILDREPOS_PERM_CATS ||= "READERS WRITERS";
my ($creator, $dummy, $dummy2) = &wild_repo_rights($repo, "");
my ($creator, $dummy, $dummy2) = wild_repo_rights($repo, "");
die "$repo doesnt exist or is not yours\n" unless $user eq $creator;
wrap_chdir("$ENV{GL_REPO_BASE_ABS}");
wrap_chdir("$repo.git");
@ -419,7 +351,7 @@ sub get_set_perms
# gitweb and daemon
setup_daemon_access($repo);
# add or delete line (arg1) from file (arg2) depending on arg3
&add_del_line ("$repo.git", $PROJECTS_LIST, &setup_gitweb_access($repo, '', ''));
add_del_line ("$repo.git", $PROJECTS_LIST, setup_gitweb_access($repo, '', ''));
}
}
@ -430,7 +362,7 @@ sub get_set_perms
sub get_set_desc
{
my($repo, $verb, $user) = @_;
my ($creator, $dummy, $dummy2) = &wild_repo_rights($repo, "");
my ($creator, $dummy, $dummy2) = wild_repo_rights($repo, "");
die "$repo doesnt exist or is not yours\n" unless $user eq $creator;
wrap_chdir("$ENV{GL_REPO_BASE_ABS}");
wrap_chdir("$repo.git");
@ -475,7 +407,7 @@ sub setup_daemon_access
{
my $repo = shift;
if (&can_read($repo, 'daemon')) {
if (can_read($repo, 'daemon')) {
system("touch $export_ok");
} else {
unlink($export_ok);
@ -517,10 +449,111 @@ sub setup_gitweb_access
system("git config --remove-section gitweb 2>/dev/null");
}
return ($desc or &can_read($repo, 'gitweb'));
return ($desc or can_read($repo, 'gitweb'));
# this return value is used by the caller to write to projects.list
}
# ----------------------------------------------------------------------------
# print a report of $user's basic permissions
# ----------------------------------------------------------------------------
sub report_version {
my($user) = @_;
print "hello $user, the gitolite version here is ";
system("cat", ($GL_PACKAGE_CONF || "$GL_ADMINDIR/conf") . "/VERSION");
}
sub perm_code {
# print the permission code
my($all, $super, $user, $x) = @_;
return " " unless $all or $super or $user;
return " $x " unless $all or $super; # only $user (explicit access) was given
my $ret;
$ret = " \@$x" if $all; # prefix @ if repo allows access for @all users
$ret = " \#$x" if $super; # prefix # if user has access to @all repos (sort of like a super user)
$ret = " \&$x" if $all and $super; # prefix & if both the above
$ret .= ($user ? " " : "_" ); # suffix _ if no explicit access else <space>
return $ret;
}
# basic means wildcards will be shown as wildcards; this is pretty much what
# got parsed by the compile script
sub report_basic
{
my($repo, $user) = @_;
# XXX The correct way is actually to give parse_acl another argument
# (defaulting to $ENV{GL_USER}, the value being used now). But for now
# this will do, even though it's a bit of a kludge to get the basic access
# rights for some other user this way
local $ENV{GL_USER} = $user;
parse_acl("", "CREATOR");
# all we need is for 'keys %repos' to come up with all the names, so:
@repos{ keys %split_conf } = values %split_conf if %split_conf;
# send back some useful info if no command was given
report_version($user);
print "\rthe gitolite config gives you the following access:\r\n";
my $count = 0;
for my $r (sort keys %repos) {
next unless $r =~ /$repo/i;
# if $GL_BIG_CONFIG is on, limit the number of output lines to 20
next if $GL_BIG_CONFIG and $count++ >= 20;
if ($r =~ $REPONAME_PATT and $r !~ /\bCREAT[EO]R\b/) {
parse_acl($r, "NOBODY");
} else {
$r =~ s/\bCREAT[EO]R\b/$user/g;
parse_acl($r, $ENV{GL_USER});
}
# @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
# R => normal access
my $perm .= ( $repos{$r}{C}{'@all'} ? ' @C' : ( $repos{$r}{C}{$user} ? ' C' : ' ' ) );
$perm .= perm_code( $repos{$r}{R}{'@all'}, $repos{'@all'}{R}{$user}, $repos{$r}{R}{$user}, 'R');
$perm .= perm_code( $repos{$r}{W}{'@all'}, $repos{'@all'}{W}{$user}, $repos{$r}{W}{$user}, 'W');
print "$perm\t$r\r\n" if $perm =~ /\S/;
}
print "only 20 out of $count candidate repos examined\r\nplease use a partial reponame or regex pattern to limit output\r\n" if $GL_BIG_CONFIG and $count > 20;
print "$GL_SITE_INFO\n" if $GL_SITE_INFO;
}
# ----------------------------------------------------------------------------
# print a report of $user's expanded permissions
# ----------------------------------------------------------------------------
sub expand_wild
{
my($repo, $user) = @_;
report_version($user);
print "\ryou have access to the following repos on the server:\r\n";
# this is for convenience; he can copy-paste the output of the basic
# access report instead of having to manually change CREATOR to his name
$repo =~ s/\bCREAT[EO]R\b/$user/g;
# display matching repos (from *all* the repos in the system) that $user
# has at least "R" access to
chdir("$ENV{GL_REPO_BASE_ABS}") or die "chdir $ENV{GL_REPO_BASE_ABS} failed: $!\n";
my $count = 0;
for my $actual_repo (`find . -type d -name "*.git"|sort`) {
chomp ($actual_repo);
$actual_repo =~ s/^\.\///;
$actual_repo =~ s/\.git$//;
# actual_repo has to match the pattern being expanded
next unless $actual_repo =~ /$repo/i;
next if $GL_BIG_CONFIG and $count++ >= 20;
my($perm, $creator, $wild) = repo_rights($actual_repo);
next unless $perm =~ /\S/;
print "$perm\t$creator\t$actual_repo\n";
}
print "only 20 out of $count candidate repos examined\nplease use a partial reponame or regex pattern to limit output\n" if $GL_BIG_CONFIG and $count > 20;
print "$GL_SITE_INFO\n" if $GL_SITE_INFO;
}
# ----------------------------------------------------------------------------
# parse the compiled acl
# ----------------------------------------------------------------------------
@ -530,8 +563,8 @@ sub parse_acl
# IMPLEMENTATION NOTE: a wee bit of this is duplicated in the update hook;
# please update that also if the interface or the env vars change
my ($GL_CONF_COMPILED, $repo, $c, %perm_cats) = @_;
my $perm_cats_sig = '';
my ($repo, $c, %perm_cats) = @_;
my $perm_cats_sig = ''; # a "signature" of the perm_cats hash
map { $perm_cats_sig .= "$_.$perm_cats{$_}," } sort keys %perm_cats;
$c = "NOBODY" unless $GL_WILDREPOS;
@ -572,8 +605,8 @@ sub parse_acl
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);
($wild, @repo_plus) = get_memberships($repo, 1);
( @user_plus) = get_memberships($gl_user, 0);
# the old "convenience copy" thing. Now on steroids :)
@ -598,8 +631,6 @@ sub parse_acl
}
}
$ENV{GL_REPOPATT} = "";
$ENV{GL_REPOPATT} = $wild if $wild and $GL_WILDREPOS;
return ($wild);
}
@ -613,108 +644,10 @@ sub add_repo_conf
$git_configs{$repo} = $one_git_config{$repo};
}
# ----------------------------------------------------------------------------
# print a report of $user's basic permissions
# repo_rights
# ----------------------------------------------------------------------------
sub report_version {
my($GL_ADMINDIR, $user) = @_;
print "hello $user, the gitolite version here is ";
system("cat", ($GL_PACKAGE_CONF || "$GL_ADMINDIR/conf") . "/VERSION");
}
sub perm_code {
# print the permission code
my($all, $super, $user, $x) = @_;
return " " unless $all or $super or $user;
return " $x " unless $all or $super; # only $user (explicit access) was given
my $ret;
$ret = " \@$x" if $all; # prefix @ if repo allows access for @all users
$ret = " \#$x" if $super; # prefix # if user has access to @all repos (sort of like a super user)
$ret = " \&$x" if $all and $super; # prefix & if both the above
$ret .= ($user ? " " : "_" ); # suffix _ if no explicit access else <space>
return $ret;
}
# basic means wildcards will be shown as wildcards; this is pretty much what
# got parsed by the compile script
sub report_basic
{
my($GL_ADMINDIR, $GL_CONF_COMPILED, $repo, $user) = @_;
# XXX The correct way is actually to give parse_acl another argument
# (defaulting to $ENV{GL_USER}, the value being used now). But for now
# this will do, even though it's a bit of a kludge to get the basic access
# rights for some other user this way
local $ENV{GL_USER} = $user;
&parse_acl($GL_CONF_COMPILED, "", "CREATOR");
# all we need is for 'keys %repos' to come up with all the names, so:
@repos{ keys %split_conf } = values %split_conf if %split_conf;
# send back some useful info if no command was given
&report_version($GL_ADMINDIR, $user);
print "\rthe gitolite config gives you the following access:\r\n";
my $count = 0;
for my $r (sort keys %repos) {
next unless $r =~ /$repo/i;
# if $GL_BIG_CONFIG is on, limit the number of output lines to 20
next if $GL_BIG_CONFIG and $count++ >= 20;
if ($r =~ $REPONAME_PATT and $r !~ /\bCREAT[EO]R\b/) {
&parse_acl($GL_CONF_COMPILED, $r, "NOBODY");
} else {
$r =~ s/\bCREAT[EO]R\b/$user/g;
&parse_acl($GL_CONF_COMPILED, $r, $ENV{GL_USER});
}
# @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
# R => normal access
my $perm .= ( $repos{$r}{C}{'@all'} ? ' @C' : ( $repos{$r}{C}{$user} ? ' C' : ' ' ) );
$perm .= &perm_code( $repos{$r}{R}{'@all'}, $repos{'@all'}{R}{$user}, $repos{$r}{R}{$user}, 'R');
$perm .= &perm_code( $repos{$r}{W}{'@all'}, $repos{'@all'}{W}{$user}, $repos{$r}{W}{$user}, 'W');
print "$perm\t$r\r\n" if $perm =~ /\S/;
}
print "only 20 out of $count candidate repos examined\r\nplease use a partial reponame or regex pattern to limit output\r\n" if $GL_BIG_CONFIG and $count > 20;
print "$GL_SITE_INFO\n" if $GL_SITE_INFO;
}
# ----------------------------------------------------------------------------
# print a report of $user's basic permissions
# ----------------------------------------------------------------------------
sub expand_wild
{
my($GL_ADMINDIR, $GL_CONF_COMPILED, $repo, $user) = @_;
&report_version($GL_ADMINDIR, $user);
print "\ryou have access to the following repos on the server:\r\n";
# this is for convenience; he can copy-paste the output of the basic
# access report instead of having to manually change CREATOR to his name
$repo =~ s/\bCREAT[EO]R\b/$user/g;
# display matching repos (from *all* the repos in the system) that $user
# has at least "R" access to
chdir("$ENV{GL_REPO_BASE_ABS}") or die "chdir $ENV{GL_REPO_BASE_ABS} failed: $!\n";
my $count = 0;
for my $actual_repo (`find . -type d -name "*.git"|sort`) {
chomp ($actual_repo);
$actual_repo =~ s/^\.\///;
$actual_repo =~ s/\.git$//;
# actual_repo has to match the pattern being expanded
next unless $actual_repo =~ /$repo/i;
next if $GL_BIG_CONFIG and $count++ >= 20;
my($perm, $creator, $wild) = &repo_rights($actual_repo);
next unless $perm =~ /\S/;
print "$perm\t$creator\t$actual_repo\n";
}
print "only 20 out of $count candidate repos examined\nplease use a partial reponame or regex pattern to limit output\n" if $GL_BIG_CONFIG and $count > 20;
print "$GL_SITE_INFO\n" if $GL_SITE_INFO;
}
# there will be multiple calls to repo_rights; better to use a closure. We
# might even be called from outside (see the admin-defined-commands docs for
# how/why). Regardless of how we're called, we assume $ENV{GL_USER} is
@ -733,7 +666,7 @@ sub expand_wild
unless ($REPO_BASE) {
# means we've been called from outside; see doc/admin-defined-commands.mkd
&where_is_rc();
where_is_rc();
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
}
@ -749,11 +682,11 @@ sub expand_wild
# "wild_repo_rights" sub for nuances.
my (%perm_cats);
# these will be empty if it's not a wildcard repo anyway
($creator, %perm_cats) = &wild_repo_rights($repo, $ENV{GL_USER});
($creator, %perm_cats) = wild_repo_rights($repo, $ENV{GL_USER});
# get access list with these substitutions
$wild = &parse_acl($GL_CONF_COMPILED, $repo, $creator || "NOBODY", %perm_cats);
$wild = parse_acl($repo, $creator || "NOBODY", %perm_cats);
} else {
$wild = &parse_acl($GL_CONF_COMPILED, $repo, $ENV{GL_USER});
$wild = parse_acl($repo, $ENV{GL_USER});
}
if ($exists) {
@ -774,8 +707,8 @@ sub expand_wild
delete $repos{$repo} if $perm !~ /C/ and $wild;
$creator = "<notfound>";
}
$perm .= &perm_code( $repos{$repo}{R}{'@all'}, $repos{'@all'}{R}{$ENV{GL_USER}}, $repos{$repo}{R}{$ENV{GL_USER}}, 'R' );
$perm .= &perm_code( $repos{$repo}{W}{'@all'}, $repos{'@all'}{W}{$ENV{GL_USER}}, $repos{$repo}{W}{$ENV{GL_USER}}, 'W' );
$perm .= perm_code( $repos{$repo}{R}{'@all'}, $repos{'@all'}{R}{$ENV{GL_USER}}, $repos{$repo}{R}{$ENV{GL_USER}}, 'R' );
$perm .= perm_code( $repos{$repo}{W}{'@all'}, $repos{'@all'}{W}{$ENV{GL_USER}}, $repos{$repo}{W}{$ENV{GL_USER}}, 'W' );
# set up for caching %repos
$last_repo = $repo;
@ -784,18 +717,22 @@ sub expand_wild
}
}
# ----------------------------------------------------------------------------
# helpers...
# ----------------------------------------------------------------------------
# helper/convenience routine to get rights and ownership from a shell command
sub cli_repo_rights {
# check_access does a lot more, so just call it. Since it returns perms
# and creator separately, just space-join them and print it.
print join(" ", &check_access($_[0])), "\n";
print join(" ", check_access($_[0])), "\n";
}
sub can_read {
my $repo = shift;
my $user = shift || $ENV{GL_USER};
local $ENV{GL_USER} = $user;
my ($perm, $creator, $wild) = &repo_rights($repo);
my ($perm, $creator, $wild) = repo_rights($repo);
return ( ($GL_ALL_INCLUDES_SPECIAL || $user !~ /^(gitweb|daemon)$/)
? $perm =~ /R/
: $perm =~ /R /
@ -812,6 +749,117 @@ sub check_repo_write_enabled {
}
}
# ----------------------------------------------------------------------------
# get memberships
# ----------------------------------------------------------------------------
# given a plain reponame or username, return:
# - the name itself if it's a user
# - the name itself if it's a repo and the repo exists in the config
# plus, if $GL_BIG_CONFIG is set:
# - all the groups the name belongs to
# plus, for repos:
# - all the wildcards matching it
# plus, if $GL_BIG_CONFIG is set:
# - all the groups those wildcards belong to
# A name can normally appear (repo example) (user example)
# - directly (repo foo) (RW = bob)
# - (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 = bob; RW = @ug))
# - (only for repos) as an indirect wildcard (@g = foo/.*; repo @g).
# note: the wildcard stuff does not apply to username memberships
our %extgroups_cache;
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 = ''; # will be a space-sep list of matching patterns
my @ret; # list of matching groups/patterns
# direct
push @ret, $base if not $is_repo or exists $repos{$base};
if ($is_repo and $GL_WILDREPOS) {
for my $i (sort keys %repos) {
next if $i eq $base; # "direct" name already done; skip
# direct wildcard
if ($base =~ /^$i$/) {
push @ret, $i;
$wild = ($wild ? "$wild $i" : $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 $base =~ /^$i$/) {
# indirect wildcard
push @ret, $g;
$wild = ($wild ? "$wild $i" : $i);
}
}
}
}
# deal with returning user info first
unless ($is_repo) {
# bring in group membership info stored externally, by running
# $GL_GET_MEMBERSHIPS_PGM if it is defined
if ($extgroups_cache{$base}) {
push @ret, @{ $extgroups_cache{$base} };
} elsif ($GL_GET_MEMBERSHIPS_PGM) {
my @extgroups = map { s/^/@/; $_; } split ' ', `$GL_GET_MEMBERSHIPS_PGM $base`;
$extgroups_cache{$base} = \@extgroups;
push @ret, @extgroups;
}
return (@ret);
}
# note that there is an extra return value when called for repos (as
# opposed to being called for usernames)
return ($wild, @ret);
}
# ----------------------------------------------------------------------------
# generic check access routine
# ----------------------------------------------------------------------------
sub check_access
{
my ($repo, $ref, $aa, $dry_run) = @_;
# aa = attempted access
my ($perm, $creator, $wild) = repo_rights($repo);
$perm =~ s/ /_/g;
$creator =~ s/^\(|\)$//g;
return ($perm, $creator) unless $ref;
# until I do some major refactoring (which will bloat the update hook a
# bit, sadly), this code duplicates stuff in the current update hook.
my @allowed_refs;
# user+repo specific perms override everything else, so they come first.
# Then perms given to specific user for @all repos, and finally perms
# given to @all users for specific repo
push @allowed_refs, @ { $repos{$repo}{$ENV{GL_USER}} || [] };
push @allowed_refs, @ { $repos{'@all'}{$ENV{GL_USER}} || [] };
push @allowed_refs, @ { $repos{$repo}{'@all'} || [] };
if ($dry_run) {
return check_ref(\@allowed_refs, $repo, $ref, $aa, $dry_run);
} else {
check_ref(\@allowed_refs, $repo, $ref, $aa);
}
}
# ----------------------------------------------------------------------------
# setup the ~/.ssh/authorized_keys file
# ----------------------------------------------------------------------------
@ -820,16 +868,16 @@ sub setup_authkeys
{
# ARGUMENTS
my($bindir, $GL_KEYDIR, $user_list_p) = @_;
my($GL_KEYDIR, $user_list_p) = @_;
# calling from outside the normal compile script may mean that argument 2
# may not be passed; so make sure it's a valid hashref, even if empty
$user_list_p = {} unless $user_list_p;
# CONSTANTS
# LOCAL CONSTANTS
# command and options for authorized_keys
my $AUTH_COMMAND="$bindir/gl-auth-command";
$AUTH_COMMAND="$bindir/gl-time $bindir/gl-auth-command" if $GL_PERFLOGT;
my $AUTH_COMMAND="$ENV{GL_BINDIR}/gl-auth-command";
$AUTH_COMMAND="$ENV{GL_BINDIR}/gl-time $ENV{GL_BINDIR}/gl-auth-command" if $GL_PERFLOGT;
my $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty";
# START
@ -931,14 +979,14 @@ sub setup_authkeys
sub special_cmd
{
my ($GL_ADMINDIR, $GL_CONF_COMPILED, $shell_allowed, $RSYNC_BASE, $HTPASSWD_FILE, $SVNSERVE) = @_;
my ($shell_allowed) = @_;
my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
my $user = $ENV{GL_USER};
# check each special command we know about and call it if enabled
if ($cmd eq 'info') {
&report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, '^', $user);
report_basic('^', $user);
print "you also have shell access\r\n" if $shell_allowed;
} elsif ($cmd =~ /^info\s+(.+)$/) {
my @otherusers = split ' ', $1;
@ -951,139 +999,69 @@ sub special_cmd
# set up the list of users being queried; it's either a list passed in
# (allowed only for admin pushers) or just $user
if (@otherusers) {
my($perm, $creator, $wild) = &repo_rights('gitolite-admin');
my($perm, $creator, $wild) = repo_rights('gitolite-admin');
die "you can't ask for others' permissions\n" unless $perm =~ /W/;
}
push @otherusers, $user unless @otherusers;
&parse_acl($GL_CONF_COMPILED);
parse_acl();
for my $otheruser (@otherusers) {
warn("ignoring illegal username $otheruser\n"), next unless $otheruser =~ $USERNAME_PATT;
&report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $repo, $otheruser);
report_basic($repo, $otheruser);
}
} elsif ($HTPASSWD_FILE and $cmd eq 'htpasswd') {
&ext_cmd_htpasswd($HTPASSWD_FILE);
ext_cmd_htpasswd($HTPASSWD_FILE);
} elsif ($RSYNC_BASE and $cmd =~ /^rsync /) {
&ext_cmd_rsync($GL_CONF_COMPILED, $RSYNC_BASE, $cmd);
ext_cmd_rsync($GL_CONF_COMPILED, $RSYNC_BASE, $cmd);
} elsif ($SVNSERVE and $cmd eq 'svnserve -t') {
&ext_cmd_svnserve($SVNSERVE);
ext_cmd_svnserve($SVNSERVE);
} else {
# if the user is allowed a shell, just run the command
&log_it();
log_it();
exec $ENV{SHELL}, "-c", $cmd if $shell_allowed;
die "bad command: $cmd\n";
}
}
# ----------------------------------------------------------------------------
# get memberships
# ----------------------------------------------------------------------------
sub run_custom_command {
my $user = shift;
# given a plain reponame or username, return:
# - the name itself if it's a user
# - the name itself if it's a repo and the repo exists in the config
# plus, if $GL_BIG_CONFIG is set:
# - all the groups the name belongs to
# plus, for repos:
# - all the wildcards matching it
# plus, if $GL_BIG_CONFIG is set:
# - all the groups those wildcards belong to
# A name can normally appear (repo example) (user example)
# - directly (repo foo) (RW = bob)
# - (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 = bob; RW = @ug))
# - (only for repos) as an indirect wildcard (@g = foo/.*; repo @g).
# note: the wildcard stuff does not apply to username memberships
our %extgroups_cache;
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 = ''; # will be a space-sep list of matching patterns
my @ret; # list of matching groups/patterns
# direct
push @ret, $base if not $is_repo or exists $repos{$base};
if ($is_repo and $GL_WILDREPOS) {
for my $i (sort keys %repos) {
next if $i eq $base; # "direct" name already done; skip
# direct wildcard
if ($base =~ /^$i$/) {
push @ret, $i;
$wild = ($wild ? "$wild $i" : $i);
my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
my ($verb, $repo) = ($cmd =~ /^\s*(\S+)(?:\s+'?\/?(.*?)(?:\.git)?'?)?$/);
# deal with "no argument" cases
$verb eq 'expand' ? $repo = '^' : die "$verb needs an argument\n" unless $repo;
if ($repo =~ $REPONAME_PATT and $verb =~ /getperms|setperms/) {
# with an actual reponame, you can "getperms" or "setperms"
get_set_perms($repo, $verb, $user);
}
elsif ($repo =~ $REPONAME_PATT and $verb =~ /(get|set)desc/) {
# with an actual reponame, you can "getdesc" or "setdesc"
get_set_desc($repo, $verb, $user);
}
}
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 $base =~ /^$i$/) {
# indirect wildcard
push @ret, $g;
$wild = ($wild ? "$wild $i" : $i);
}
}
}
}
# deal with returning user info first
unless ($is_repo) {
# bring in group membership info stored externally, by running
# $GL_GET_MEMBERSHIPS_PGM if it is defined
if ($extgroups_cache{$base}) {
push @ret, @{ $extgroups_cache{$base} };
} elsif ($GL_GET_MEMBERSHIPS_PGM) {
my @extgroups = map { s/^/@/; $_; } split ' ', `$GL_GET_MEMBERSHIPS_PGM $base`;
$extgroups_cache{$base} = \@extgroups;
push @ret, @extgroups;
}
return (@ret);
}
# note that there is an extra return value when called for repos (as
# opposed to being called for usernames)
return ($wild, @ret);
}
# ----------------------------------------------------------------------------
# generic check access routine
# ----------------------------------------------------------------------------
sub check_access
{
my ($repo, $ref, $aa, $dry_run) = @_;
# aa = attempted access
my ($perm, $creator, $wild) = &repo_rights($repo);
$perm =~ s/ /_/g;
$creator =~ s/^\(|\)$//g;
return ($perm, $creator) unless $ref;
# until I do some major refactoring (which will bloat the update hook a
# bit, sadly), this code duplicates stuff in the current update hook.
my @allowed_refs;
# user+repo specific perms override everything else, so they come first.
# Then perms given to specific user for @all repos, and finally perms
# given to @all users for specific repo
push @allowed_refs, @ { $repos{$repo}{$ENV{GL_USER}} || [] };
push @allowed_refs, @ { $repos{'@all'}{$ENV{GL_USER}} || [] };
push @allowed_refs, @ { $repos{$repo}{'@all'} || [] };
if ($dry_run) {
return &check_ref(\@allowed_refs, $repo, $ref, $aa, $dry_run);
elsif ($verb eq 'expand') {
# with a wildcard, you can "expand" it to see what repos actually match
die "$repo has invalid characters" unless "x$repo" =~ $REPOPATT_PATT;
expand_wild($repo, $user);
} else {
&check_ref(\@allowed_refs, $repo, $ref, $aa);
die "$cmd doesn't make sense to me\n";
}
}
sub shell_out {
my $shell = $ENV{SHELL};
$shell =~ s/.*\//-/; # change "/bin/bash" to "-bash"
log_it($shell);
exec { $ENV{SHELL} } $shell;
}
sub try_adc {
my ($cmd, @args) = split ' ', $ENV{SSH_ORIGINAL_COMMAND};
if (-x "$GL_ADC_PATH/$cmd") {
# yes this is rather strict, sorry.
do { die "I don't like $_\n" unless $_ =~ $ADC_CMD_ARGS_PATT } for ($cmd, @args);
log_it("$GL_ADC_PATH/$ENV{SSH_ORIGINAL_COMMAND}");
exec("$GL_ADC_PATH/$cmd", @args);
}
}
@ -1114,11 +1092,11 @@ sub ext_cmd_rsync
# ok now check if we're permitted to execute a $perm action on $path
# (taken as a refex) using rsync.
&check_access('EXTCMD/rsync', "NAME/$path", $perm);
check_access('EXTCMD/rsync', "NAME/$path", $perm);
# that should "die" if there's a problem
wrap_chdir($RSYNC_BASE);
&log_it();
log_it();
exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND};
}
@ -1162,4 +1140,6 @@ sub ext_cmd_svnserve
die "svnserve exec failed\n";
}
# ------------------------------------------------------------------------------
# per perl rules, this should be the last line in such a file:
1;

157
src/gitolite_env.pm Normal file
View file

@ -0,0 +1,157 @@
# stuff that detects or sets up the runtime environment
package gitolite_env;
use Exporter 'import';
@EXPORT = qw(
setup_environment
simulate_ssh_connection
get_logfilename
);
use strict;
use warnings;
# ----------------------------------------------------------------------------
# find the rc file, then pull the libraries
# ----------------------------------------------------------------------------
BEGIN {
die "ENV GL_RC not set\n" unless $ENV{GL_RC};
die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR};
}
use lib $ENV{GL_BINDIR};
use gitolite_rc;
use gitolite;
# ----------------------------------------------------------------------------
# start
# ----------------------------------------------------------------------------
# firstly, the following function, 'setup_environment', is not only about env
# vars; it does other stuff too (like umask, nice...)
# a lot of stuff gets carried around in env vars primarily for 2 reasons. One
# is that git calls the hooks, so they're not in the same 'process' as the
# 'gl-auth-command' that probably started things off.
# Granted; we could write the same 'discovery' within the hook code, but
# that's needless code duplication, plus in some cases a good amount of
# inefficiency.
# Even more important, we do *not* want to burden the ADCs (admin defined
# commands) with all this discovery, because those are written by the users
# themselves (my 'user' == some gitolite 'admin' somewhere; I don't mean
# 'gitolite user')
# think of it OS-supported memo-ization :-)
sub setup_environment {
$ENV{GL_ADMINDIR} = $GL_ADMINDIR;
$ENV{GL_LOG} = get_logfilename($GL_LOGT);
$ENV{PATH} = "$GIT_PATH:$ENV{PATH}" if $GIT_PATH;
# set default permission of wildcard repositories
$ENV{GL_WILDREPOS_DEFPERMS} = $GL_WILDREPOS_DEFPERMS if $GL_WILDREPOS_DEFPERMS;
# this is used in so many places, inside and outside gitolite by external
# hooks and ADCs, it isn't even funny...
$ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
# be nice if asked. If you want me to pull in BSD::Resource to get rid of
# the first '0', feel free to send me a patch that does everything needed
# from within my own installer, does not require internet access (don't
# ask!), and doesn't require a C compiler or the perl-devel (or eqvt
# named) packages. Heck in some cases it's not even Linux...
setpriority(0, 0, $GL_NICE_VALUE) if $GL_NICE_VALUE and $GL_NICE_VALUE > 0;
umask($REPO_UMASK);
set_up_http_death() if $ENV{GITOLITE_HTTP_HOME};
}
sub simulate_ssh_connection {
# these patterns indicate normal git usage; see "services[]" in
# http-backend.c for how I got that. Also note that "info" is overloaded;
# git uses "info/refs...", while gitolite uses "info" or "info?...". So
# there's a "/" after info in the list below
if ($ENV{PATH_INFO} =~ m(^/(.*)/(HEAD$|info/refs$|objects/|git-(?:upload|receive)-pack$))) {
my $repo = $1;
my $verb = ($ENV{REQUEST_URI} =~ /git-receive-pack/) ? 'git-receive-pack' : 'git-upload-pack';
$ENV{SSH_ORIGINAL_COMMAND} = "$verb '$repo'";
} else {
# this is one of our custom commands; could be anything really,
# because of the adc feature
my ($verb) = ($ENV{PATH_INFO} =~ m(^/(\S+)));
my $args = $ENV{QUERY_STRING};
$args =~ s/\+/ /g;
$ENV{SSH_ORIGINAL_COMMAND} = $verb;
$ENV{SSH_ORIGINAL_COMMAND} .= " $args" if $args;
print_http_headers(); # in preparation for the eventual output!
}
$ENV{SSH_CONNECTION} = "$ENV{REMOTE_ADDR} $ENV{REMOTE_PORT} $ENV{SERVER_ADDR} $ENV{SERVER_PORT}";
}
# a plain "die" was fine for ssh but http has all that extra gunk it needs.
# So we need to, in effect, create a "death handler".
sub set_up_http_death
{
$SIG{__DIE__} = sub {
my $service = ($ENV{SSH_ORIGINAL_COMMAND} =~ /git-receive-pack/ ? 'git-receive-pack' : 'git-upload-pack');
my $message = shift; chomp($message);
print STDERR "$message\n";
# format the service response, then the message. With initial
# help from Ilari and then a more detailed email from Shawn...
$service = "# service=$service\n"; $message = "ERR $message\n";
$service = sprintf("%04X", length($service)+4) . "$service"; # no CRLF on this one
$message = sprintf("%04X", length($message)+4) . "$message";
print_http_headers();
print $service;
print "0000"; # flush-pkt, apparently
print $message;
print STDERR $service;
print STDERR $message;
exit 0; # if it's ok for die_webcgi in git.git/http-backend.c, it's ok for me ;-)
}
}
# ----------------------------------------------------------------------------
# helpers
# ----------------------------------------------------------------------------
my $http_headers_printed = 0;
sub print_http_headers {
my($code, $text) = @_;
return if $http_headers_printed++;
$code ||= 200;
$text ||= "OK - gitolite";
$|++;
print "Status: $code $text\r\n";
print "Expires: Fri, 01 Jan 1980 00:00:00 GMT\r\n";
print "Pragma: no-cache\r\n";
print "Cache-Control: no-cache, max-age=0, must-revalidate\r\n";
print "\r\n";
}
sub get_logfilename {
# this sub has a wee little side-effect; it sets $ENV{GL_TS}
my($template) = shift;
my ($s, $min, $h, $d, $m, $y) = (localtime)[0..5];
$y += 1900; $m++; # usual adjustments
for ($s, $min, $h, $d, $m) {
$_ = "0$_" if $_ < 10;
}
$ENV{GL_TS} = "$y-$m-$d.$h:$min:$s";
# substitute template parameters and set the logfile name
$template =~ s/%y/$y/g;
$template =~ s/%m/$m/g;
$template =~ s/%d/$d/g;
return ($template);
}
# ------------------------------------------------------------------------------
# per perl rules, this should be the last line in such a file:
1;

68
src/gitolite_rc.pm Normal file
View file

@ -0,0 +1,68 @@
# stuff to help pull in the rc file, plus various constants
package gitolite_rc;
use Exporter 'import';
# the first set (before the blank line) are constants defined right here in
# this program. The second set are from the 'rc'; We're clubbing all in
# because they're all "constants" in a programmatic sense
@EXPORT = qw(
$ABRT $WARN
$R_COMMANDS $W_COMMANDS
$REPONAME_PATT $USERNAME_PATT $REPOPATT_PATT
$ADC_CMD_ARGS_PATT
$current_data_version
$ADMIN_POST_UPDATE_CHAINS_TO $ENV $GITOLITE_BASE $GITOLITE_PATH $GIT_PATH
$GL_ADC_PATH $GL_ADMINDIR $GL_ALL_INCLUDES_SPECIAL $GL_ALL_READ_ALL
$GL_BIG_CONFIG $GL_CONF $GL_CONF_COMPILED $GL_GET_MEMBERSHIPS_PGM
$GL_GITCONFIG_KEYS $GL_GITCONFIG_WILD $GL_KEYDIR $GL_LOGT $GL_NICE_VALUE
$GL_NO_CREATE_REPOS $GL_NO_DAEMON_NO_GITWEB $GL_NO_SETUP_AUTHKEYS
$GL_PACKAGE_CONF $GL_PACKAGE_HOOKS $GL_PERFLOGT $GL_SITE_INFO
$GL_SLAVE_MODE $GL_WILDREPOS $GL_WILDREPOS_DEFPERMS
$GL_WILDREPOS_PERM_CATS $HTPASSWD_FILE $PROJECTS_LIST $REPO_BASE
$REPO_UMASK $RSYNC_BASE $SVNSERVE $UPDATE_CHAINS_TO
);
# ------------------------------------------------------------------------------
# bring in the rc vars and allow querying them
# ------------------------------------------------------------------------------
# in case we're running under Apache using smart http
$ENV{HOME} = $ENV{GITOLITE_HTTP_HOME} if $ENV{GITOLITE_HTTP_HOME};
# we also need to "bring in" the rc variables. The rc can only be in one of
# these two places; the first one we find, wins
for ("$ENV{HOME}/.gitolite.rc", "/etc/gitolite/gitolite.rc") {
$ENV{GL_RC} ||= $_ if -f;
}
die "no rc file found\n" unless $ENV{GL_RC};
do $ENV{GL_RC} or die "error parsing $ENV{GL_RC}\n";
# ------------------------------------------------------------------------------
# real constants
# ------------------------------------------------------------------------------
$current_data_version = '1.7';
$ABRT = "\n\t\t***** ABORTING *****\n ";
$WARN = "\n\t\t***** WARNING *****\n ";
# commands we're expecting
$R_COMMANDS=qr/^(git[ -]upload-pack|git[ -]upload-archive)$/;
$W_COMMANDS=qr/^git[ -]receive-pack$/;
# note that REPONAME_PATT allows "/", while USERNAME_PATT does not
# also, the reason REPONAME_PATT is a superset of USERNAME_PATT is (duh!)
# because a repo can have "CREATOR" in the name
$REPONAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@/+-]*$);
$USERNAME_PATT=qr(^\@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$);
# same as REPONAME, but used for wildcard repos, allows some common regex metas
$REPOPATT_PATT=qr(^\@?[0-9a-zA-Z[][\\^.$|()[\]*+?{}0-9a-zA-Z._\@/-]*$);
# ADC commands and arguments must match this pattern
$ADC_CMD_ARGS_PATT=qr(^[0-9a-zA-Z._\@/+:-]*$);
# ------------------------------------------------------------------------------
# per perl rules, this should be the last line in such a file:
1;

View file

@ -1,17 +1,9 @@
#!/usr/bin/perl
use strict;
use warnings;
# ----------------------------------------------------------------------------
# you: what's the invocation?
# me: Hail, O Lord Ganesha, destroyer of obsta...
# you: err hmm not *that* sort of invocation... I meant how does this program
# get invoked?
# me: oh hehe <hides sheepish grin>, ok here we go...
#
# ssh mode
# - started by sshd
# - one optional flag, "-s", for "shell allowed" people
# - one argument, the "user" name
# - one env var, SSH_ORIGINAL_COMMAND, containing the command
# - command typically: git-(receive|upload)-pack 'reponame(.git)?'
@ -27,87 +19,61 @@ use warnings;
# - no special processing commands currently handled
# ----------------------------------------------------------------------------
use strict;
use warnings;
# ----------------------------------------------------------------------------
# common definitions
# find the rc file, then pull the libraries in
# ----------------------------------------------------------------------------
# these are set by the "rc" file
our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE, $HTPASSWD_FILE, $GL_WILDREPOS, $GL_WILDREPOS_DEFPERMS, $GL_ADC_PATH, $SVNSERVE, $PROJECTS_LIST, $GL_SLAVE_MODE, $GL_PERFLOGT, $GL_ALL_READ_ALL);
# and these are set by gitolite.pm
our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT, $REPOPATT_PATT, $ADC_CMD_ARGS_PATT);
our %repos;
our %groups;
our %git_configs;
our %split_conf;;
# this (gl-auth-command) is one of the two valid starting points for all of
# gitolite for normal operations (the other being gl-time). All other
# programs are invoked either from this, or from something else (typically
# git-*-pack) in between). They thus get the benefit of the environment
# variables that this code sets up.
BEGIN {
# find and set bin dir
$0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ($1 || "$ENV{PWD}/") . $2;
}
# the common setup module is in the same directory as this running program is
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//;
unshift @INC, $bindir;
require gitolite or die "parse gitolite.pm failed\n";
# our libraries are either in the same place the scripts are, or, as with
# RPM/DEB install, in some 'system' location that is already in perl's @INC
# anyway
use lib $ENV{GL_BINDIR};
# ask where the rc file is, get it, and "do" it
&where_is_rc();
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
# we need to pass GL_ADMINDIR and the bindir to the child hooks
$ENV{GL_ADMINDIR} = $GL_ADMINDIR;
$ENV{GL_BINDIR} = $bindir;
# add a custom path for git binaries, if specified
$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH;
# set default permission of wildcard repositories
$ENV{GL_WILDREPOS_DEFPERMS} = $GL_WILDREPOS_DEFPERMS if $GL_WILDREPOS_DEFPERMS;
# set the umask before creating any files
umask($REPO_UMASK);
$ENV{GL_REPO_BASE_ABS} = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
use gitolite_rc; # this does a "do" of the rc file
use gitolite_env;
use gitolite;
# ----------------------------------------------------------------------------
# start...
# ----------------------------------------------------------------------------
# if the first argument is a "-s", this user is allowed to get a shell using
# this key
my $shell_allowed = 0;
if (@ARGV and $ARGV[0] eq '-s') {
$shell_allowed = 1;
shift;
}
# these two options are mutually exclusive. And this program is not supposed
# to be called manually anyway
my $shell_allowed = (@ARGV and $ARGV[0] eq '-s' and shift);
my $program = (@ARGV and $ARGV[0] eq '-e' and shift);
# setup the environment for the kids so they don't need to embark on the
# voyage of self-discovery above ;-) [environment also means things like
# nice, umask, etc., not just the environment *variables*]
setup_environment();
# if one of the other programs is being invoked (see doc/hacking.mkd), exec it
exec(@ARGV) if $program;
# ----------------------------------------------------------------------------
# set up SSH_ORIGINAL_COMMAND and SSH_CONNECTION in http mode
# set up GL_USER and (if reqd) SSH_ORIGINAL_COMMAND and SSH_CONNECTION
# ----------------------------------------------------------------------------
# fake out SSH_ORIGINAL_COMMAND and SSH_CONNECTION so the rest of the code
# stays the same (except the exec at the end).
my $user;
if ($ENV{REQUEST_URI}) {
die "fallback to DAV not supported\n" if $ENV{REQUEST_METHOD} eq 'PROPFIND';
# these patterns indicate normal git usage; see "services[]" in
# http-backend.c for how I got that. Also note that "info" is overloaded;
# git uses "info/refs...", while gitolite uses "info" or "info?...". So
# there's a "/" after info in the list below
if ($ENV{PATH_INFO} =~ m(^/(.*)/(HEAD$|info/refs$|objects/|git-(?:upload|receive)-pack$))) {
my $repo = $1;
my $verb = ($ENV{REQUEST_URI} =~ /git-receive-pack/) ? 'git-receive-pack' : 'git-upload-pack';
$ENV{SSH_ORIGINAL_COMMAND} = "$verb '$repo'";
} else {
# this is one of our custom commands; could be anything really,
# because of the adc feature
my ($verb) = ($ENV{PATH_INFO} =~ m(^/(\S+)));
my $args = $ENV{QUERY_STRING};
$args =~ s/\+/ /g;
$ENV{SSH_ORIGINAL_COMMAND} = $verb;
$ENV{SSH_ORIGINAL_COMMAND} .= " $args" if $args;
&print_http_headers(); # in preparation for the eventual output!
}
$ENV{SSH_CONNECTION} = "$ENV{REMOTE_ADDR} $ENV{REMOTE_PORT} $ENV{SERVER_ADDR} $ENV{SERVER_PORT}";
# fake out SSH_ORIGINAL_COMMAND and SSH_CONNECTION when called via http,
# so the rest of the code stays the same (except the exec at the end).
simulate_ssh_connection();
$user = $ENV{GL_USER} = $ENV{REMOTE_USER};
} else {
# no (more) arguments given in ssh mode? default user is $USER
@ -117,90 +83,37 @@ if ($ENV{REQUEST_URI}) {
}
# ----------------------------------------------------------------------------
# logging, timestamp env vars
# SSH_ORIGINAL_COMMAND
# ----------------------------------------------------------------------------
$ENV{GL_LOG} = &get_logfilename($GL_LOGT);
# ----------------------------------------------------------------------------
# sanity checks on SSH_ORIGINAL_COMMAND
# ----------------------------------------------------------------------------
# no SSH_ORIGINAL_COMMAND given...
# no SSH_ORIGINAL_COMMAND given: shell out or default to 'info'
unless ($ENV{SSH_ORIGINAL_COMMAND}) {
# if the user is allowed to use a shell, give him one
if ($shell_allowed) {
my $shell = $ENV{SHELL};
$shell =~ s/.*\//-/; # change "/bin/bash" to "-bash"
&log_it($shell);
exec { $ENV{SHELL} } $shell;
}
# otherwise, pretend he typed in "info" and carry on...
shell_out() if $shell_allowed; # doesn't return ('exec's out)
$ENV{SSH_ORIGINAL_COMMAND} = 'info';
}
# ----------------------------------------------------------------------------
# slave mode should not do much
# ----------------------------------------------------------------------------
die "server is in slave mode; you can only fetch\n"
if ($GL_SLAVE_MODE and $ENV{SSH_ORIGINAL_COMMAND} !~ /^(info|expand|get|git-upload-)/);
# ----------------------------------------------------------------------------
# admin defined commands
# ----------------------------------------------------------------------------
# please see doc/admin-defined-commands.mkd for details
# admin defined commands; please see doc/admin-defined-commands.mkd
if ($GL_ADC_PATH and -d $GL_ADC_PATH) {
my ($cmd, @args) = split ' ', $ENV{SSH_ORIGINAL_COMMAND};
if (-x "$GL_ADC_PATH/$cmd") {
# yes this is rather strict, sorry.
do { die "I don't like $_\n" unless $_ =~ $ADC_CMD_ARGS_PATT } for ($cmd, @args);
&log_it("$GL_ADC_PATH/$ENV{SSH_ORIGINAL_COMMAND}");
exec("$GL_ADC_PATH/$cmd", @args);
}
try_adc(); # if it succeeds, this also 'exec's out
}
# ----------------------------------------------------------------------------
# get and set perms for actual repo created by wildcard-autoviv
# ----------------------------------------------------------------------------
# get/set perms/desc for wild repos; also the 'expand' command
my $CUSTOM_COMMANDS=qr/^\s*(expand|(get|set)(perms|desc))\b/;
# note that all the subs called here chdir somewhere else and do not come
# back; they all blithely take advantage of the fact that processing custom
# commands is sort of a dead end for normal (git) processing
if ($ENV{SSH_ORIGINAL_COMMAND} =~ $CUSTOM_COMMANDS) {
die "wildrepos disabled, sorry\n" unless $GL_WILDREPOS;
my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
my ($verb, $repo) = ($cmd =~ /^\s*(\S+)(?:\s+'?\/?(.*?)(?:\.git)?'?)?$/);
# deal with "no argument" cases
$verb eq 'expand' ? $repo = '^' : die "$verb needs an argument\n" unless $repo;
if ($repo =~ $REPONAME_PATT and $verb =~ /getperms|setperms/) {
# with an actual reponame, you can "getperms" or "setperms"
get_set_perms($repo, $verb, $user);
}
elsif ($repo =~ $REPONAME_PATT and $verb =~ /(get|set)desc/) {
# with an actual reponame, you can "getdesc" or "setdesc"
get_set_desc($repo, $verb, $user);
}
elsif ($verb eq 'expand') {
# with a wildcard, you can "expand" it to see what repos actually match
die "$repo has invalid characters" unless "x$repo" =~ $REPOPATT_PATT;
expand_wild($GL_ADMINDIR, $GL_CONF_COMPILED, $repo, $user);
} else {
die "$cmd doesn't make sense to me\n";
}
run_custom_command($user);
exit 0;
}
# ----------------------------------------------------------------------------
# non-git commands
# ----------------------------------------------------------------------------
# if the command does NOT fit the pattern of a normal git command, send it off
# somewhere else...
# non-git commands: if the command does NOT fit the pattern of a normal git
# command, send it off somewhere else...
# side notes on detecting a normal git command: the pattern we check allows
# old style as well as new style ("git-subcommand arg" or "git subcommand
@ -210,63 +123,53 @@ if ($ENV{SSH_ORIGINAL_COMMAND} =~ $CUSTOM_COMMANDS) {
my ($verb, $repo) = ($ENV{SSH_ORIGINAL_COMMAND} =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:\.git)?'/);
unless ( $verb and ( $verb eq 'git-init' or $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ) {
# ok, it's not a normal git command; call the special command helper
&special_cmd ($GL_ADMINDIR, $GL_CONF_COMPILED, $shell_allowed, $RSYNC_BASE, $HTPASSWD_FILE, $SVNSERVE);
exit;
special_cmd ($shell_allowed);
exit 0;
}
# some final sanity checks
die "$repo ends with a slash; I don't like that\n" if $repo =~ /\/$/;
die "$repo has two consecutive periods; I don't like that\n" if $repo =~ /\.\./;
# reponame
# save the reponame; too many things need this
$ENV{GL_REPO}=$repo;
# ----------------------------------------------------------------------------
# the real git commands (git-receive-pack, etc...)
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# first level permissions check
# ----------------------------------------------------------------------------
my ($perm, $creator, $wild);
if ( $GL_ALL_READ_ALL and $verb =~ $R_COMMANDS and -d "$ENV{GL_REPO_BASE_ABS}/$repo.git") {
$perm = 'R';
} else {
($perm, $creator, $wild) = &repo_rights($repo);
}
if ($perm =~ /C/) {
# it was missing, and you have create perms
wrap_chdir("$ENV{GL_REPO_BASE_ABS}");
new_repo($repo, "$GL_ADMINDIR/hooks/common", $user);
# note pwd is now the bare "repo.git"; new_repo does that...
wrap_print("gl-perms", "$GL_WILDREPOS_DEFPERMS\n") if $GL_WILDREPOS_DEFPERMS;
&setup_git_configs($repo, \%git_configs);
&setup_daemon_access($repo);
&add_del_line ("$repo.git", $PROJECTS_LIST, &setup_gitweb_access($repo, '', ''));
wrap_chdir($ENV{HOME});
($perm, $creator, $wild) = repo_rights($repo);
}
# it was missing, and you have create perms, so create it
new_wild_repo($repo, $user) if ($perm =~ /C/);
# we know the user and repo; we just need to know what perm he's trying
# aa == attempted access
# we know the user and repo; we just need to know what perm he's trying for
# (aa == attempted access)
my $aa = ($verb =~ $R_COMMANDS ? 'R' : 'W');
die "$aa access for $repo DENIED to $user
(Or there may be no repository at the given path. Did you spell it correctly?)\n" unless $perm =~ /$aa/;
# check if repo is write-enabled
&check_repo_write_enabled($repo) if $aa eq 'W';
check_repo_write_enabled($repo) if $aa eq 'W';
# ----------------------------------------------------------------------------
# over to git now
# ----------------------------------------------------------------------------
if ($ENV{REQUEST_URI}) {
&log_it($ENV{REQUEST_URI});
log_it($ENV{REQUEST_URI});
exec $ENV{GIT_HTTP_BACKEND};
# the GIT_HTTP_BACKEND env var should be set either by the rc file, or as
# a SetEnv in the apache config somewhere
}
&log_it();
log_it();
$repo = "'$REPO_BASE/$repo.git'";
exec("git", "shell", "-c", "$verb $repo") unless $verb eq 'git-init';

View file

@ -6,70 +6,24 @@ use Data::Dumper;
$Data::Dumper::Indent = 1;
$Data::Dumper::Sortkeys = 1;
# ----------------------------------------------------------------------------
# find the rc file, then pull the libraries
# ----------------------------------------------------------------------------
BEGIN {
die "ENV GL_RC not set\n" unless $ENV{GL_RC};
die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR};
}
use lib $ENV{GL_BINDIR};
use gitolite_rc;
use gitolite qw(:DEFAULT %repos %groups %git_configs %split_conf);
# === add-auth-keys ===
# part of the gitolite (GL) suite
# (1) - "compiles" ~/.ssh/authorized_keys from the list of pub-keys
# (2) - also "compiles" the user-friendly GL conf file into something easier
# to parse. We're doing this because both the gl-auth-command and the
# (gl-)update hook need this, and it seems easier to do this than
# replicate the parsing code in both those places. As a bonus, it's
# probably more efficient.
# (3) - finally does what I have resisted doing all along -- handle gitweb and
# git-daemon access. It won't *setup* gitweb/daemon for you -- you have
# to that yourself. What this does is make sure that "repo.git"
# contains the file "git-daemon-export-ok" (for daemon case) and the
# line "repo.git" exists in the "projects.list" file (for gitweb case).
# how run: manual, by GL admin
# when:
# - anytime a pubkey is added/deleted
# - anytime gitolite.conf is changed
# input:
# - GL_CONF (default: ~/.gitolite/conf/gitolite.conf)
# - GL_KEYDIR (default: ~/.gitolite/keydir)
# output:
# - ~/.ssh/authorized_keys (dictated by sshd)
# - GL_CONF_COMPILED (default: ~/.gitolite/conf/gitolite.conf-compiled.pm)
# security:
# - touches a very critical system file that manages the restrictions on
# incoming users. Be sure to audit AUTH_COMMAND and AUTH_OPTIONS (see
# below) on any change to this script
# - no security checks within program. The GL admin runs this manually
# warnings:
# - if the "start" line exists, but the "end" line does not, you lose the
# rest of the existing authkey file. In general, "don't do that (TM)",
# but we do have a "vim -d" popping up so you can see the changes being
# made, just in case...
# ----------------------------------------------------------------------------
# common definitions
# ----------------------------------------------------------------------------
# setup quiet mode if asked; please do not use this when running manually
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_GITCONFIG_WILD, $GL_PACKAGE_HOOKS, $GL_BIG_CONFIG, $GL_NO_DAEMON_NO_GITWEB, $GL_NO_CREATE_REPOS, $GL_NO_SETUP_AUTHKEYS, $GL_PERFLOGT);
# and these are set by gitolite.pm
our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $ABRT, $WARN);
# the common setup module is in the same directory as this running program is
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//;
unshift @INC, $bindir;
require gitolite or die "parse gitolite.pm failed\n";
# ask where the rc file is, get it, and "do" it
&where_is_rc();
die "$ABRT parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
# add a custom path for git binaries, if specified
$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH;
# ----------------------------------------------------------------------------
# definitions specific to this program
# ----------------------------------------------------------------------------
@ -78,7 +32,7 @@ $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH;
# $groups{group}{member} = "master" (or name of fragment file in which the
# group is defined).
our %groups = ();
# our %groups = (); # moved to gitolite.pm now
# %repos has two functions.
@ -90,10 +44,10 @@ our %groups = ();
# level 2 check. In order to allow "exclude" rules, the order of rules now
# matters, so what used to be entirely "hash of hash of hash" now has a list
# in between :)
my %repos = ();
# copy above desc to lite.pm -- my %repos = ();
# repos whose ACLs don't make it into the main compiled config file
my %split_conf = ();
# names of repos whose ACLs don't make it into the main compiled config file
# copy above desc to lite.pm -- my %split_conf = ();
# rule sequence number
my $rule_seq = 0;
@ -103,21 +57,16 @@ my $rule_seq = 0;
# multiple times for the same repo+user. So...
my %rurp_seen = ();
our $current_data_version; # this comes from gitolite.pm
# catch usernames<->pubkeys mismatches; search for "lint" below
my %user_list = ();
# repo specific 'git config' stuff
our %git_configs = ();
# our %git_configs = (); # moved to gitolite.pm now
# gitweb descriptions and owners; plain text, keyed by "$repo.git"
my %desc = ();
my %owner = ();
# set the umask before creating any files
umask($REPO_UMASK);
# ----------------------------------------------------------------------------
# subroutines
# ----------------------------------------------------------------------------
@ -401,28 +350,6 @@ for my $fragment_file (glob("conf/fragments/*.conf"))
parse_conf_file($fragment_file, $fragment);
}
sub write_compiled_conf
{
my $compiled_fh = wrap_open( ">", "$GL_CONF_COMPILED.new" );
my $data_version = $current_data_version;
print $compiled_fh Data::Dumper->Dump([$data_version], [qw(*data_version)]);
my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]);
$dumped_data .= Data::Dumper->Dump([\%git_configs], [qw(*git_configs)]) if %git_configs;
# the dump uses single quotes, but we convert any strings containing $creator
# and $gl_user to double quoted strings. A bit sneaky, but not too much...
$dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
print $compiled_fh $dumped_data;
if (%groups) {
$dumped_data = Data::Dumper->Dump([\%groups], [qw(*groups)]);
$dumped_data =~ s/\bCREAT[EO]R\b/\$creator/g;
$dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
print $compiled_fh $dumped_data;
}
print $compiled_fh Data::Dumper->Dump([\%split_conf], [qw(*split_conf)]) if %split_conf;
close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n";
rename "$GL_CONF_COMPILED.new", "$GL_CONF_COMPILED";
}
# ----------------------------------------------------------------------------
# (that ends the config file compiler, though we postpone the writing
# for now to deal with the latest GL_BIG_CONFIG innovation!)
@ -539,12 +466,34 @@ sub write_1_compiled_conf
$split_conf{$repo} = 1;
}
sub write_compiled_conf
{
my $compiled_fh = wrap_open( ">", "$GL_CONF_COMPILED.new" );
my $data_version = $current_data_version;
print $compiled_fh Data::Dumper->Dump([$data_version], [qw(*data_version)]);
my $dumped_data = Data::Dumper->Dump([\%repos], [qw(*repos)]);
$dumped_data .= Data::Dumper->Dump([\%git_configs], [qw(*git_configs)]) if %git_configs;
# the dump uses single quotes, but we convert any strings containing $creator
# and $gl_user to double quoted strings. A bit sneaky, but not too much...
$dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
print $compiled_fh $dumped_data;
if (%groups) {
$dumped_data = Data::Dumper->Dump([\%groups], [qw(*groups)]);
$dumped_data =~ s/\bCREAT[EO]R\b/\$creator/g;
$dumped_data =~ s/'(?=[^']*\$(?:creator|gl_user))~?(.*?)'/"$1"/g;
print $compiled_fh $dumped_data;
}
print $compiled_fh Data::Dumper->Dump([\%split_conf], [qw(*split_conf)]) if %split_conf;
close $compiled_fh or die "$ABRT close compiled-conf failed: $!\n";
rename "$GL_CONF_COMPILED.new", "$GL_CONF_COMPILED";
}
# ----------------------------------------------------------------------------
# get a list of physical repos for later
# ----------------------------------------------------------------------------
my @phy_repos = ();
@phy_repos = &list_phy_repos() unless $GL_NO_DAEMON_NO_GITWEB;
@phy_repos = list_phy_repos() unless $GL_NO_DAEMON_NO_GITWEB;
# NOTE: we're overloading GL_NO_DAEMON_NO_GITWEB to mean "no git config" also.
# In fact anything that requires trawling through the existing repos doing
@ -573,7 +522,7 @@ my %projlist = ();
for my $repo (@phy_repos) {
wrap_chdir("$ENV{GL_REPO_BASE_ABS}/$repo.git");
# daemon is easy
&setup_daemon_access($repo);
setup_daemon_access($repo);
}
for my $repo (@phy_repos) {
@ -588,7 +537,7 @@ for my $repo (@phy_repos) {
# into the "repo foo" section; they're essentialy independent.
# Anyway, I believe it doesn't make sense to have all wild repos
# (for some pattern) to have the same description and owner.
$projlist{"$repo.git"} = 1 if &setup_gitweb_access($repo, $desc{"$repo.git"} || '', $owner{"$repo.git"} || '');
$projlist{"$repo.git"} = 1 if setup_gitweb_access($repo, $desc{"$repo.git"} || '', $owner{"$repo.git"} || '');
# git config
# implementation note: this must happen *after* one of the previous 2
@ -597,7 +546,7 @@ for my $repo (@phy_repos) {
# set for the *current* repo, which in turn stores translated values for
# $creator in the git_configs hash, which, (phew!) is needed for a match
# that eventually gets you a valid $git_configs{} below
&setup_git_configs($repo, \%git_configs) if $git_configs{$repo};
setup_git_configs($repo, \%git_configs) if $git_configs{$repo};
}
# write out the project list
@ -612,5 +561,5 @@ close $projlist_fh;
# ----------------------------------------------------------------------------
unless ($GL_NO_SETUP_AUTHKEYS) {
&setup_authkeys($bindir, $GL_KEYDIR, \%user_list);
setup_authkeys($GL_KEYDIR, \%user_list);
}

View file

@ -62,10 +62,19 @@ else
fi
# ------------------------------------------------------------------------
# setup stuff
REPO_BASE=$( cd $HOME; perl -e 'do ".gitolite.rc"; print $REPO_BASE' )
GL_BINDIR=$( cd $HOME; perl -ne 'print($1), exit if /^command="(.*?)\/gl-auth-command /' < $HOME/.ssh/authorized_keys)
GL_ADMINDIR=$(cd $HOME; perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR')
# setup stuff. Note that for *this* program, we don't want to rely on $0
# telling us bindir; the user should be allowed to run it from anywhere and
# still have it work. Luckily, by the time you feel the need to run this
# program, authkeys is already populated, and anyway that's the only
# *reliable* place to get this info. However, when running in HTTP mode or
# Fedora mode, you have *no* keys in the authkeys file. In those cases you
# have to manually set GL_BINDIR externally before running this program
[ -z "$GL_BINDIR" ] &&
GL_BINDIR=$( perl -ne 'print($1), exit if /^command="(.+?)\/gl-(time|auth-command) /' < $HOME/.ssh/authorized_keys)
GL_RC=$( $GL_BINDIR/gl-query-rc GL_RC)
REPO_BASE=$( $GL_BINDIR/gl-query-rc REPO_BASE)
GL_ADMINDIR=$($GL_BINDIR/gl-query-rc GL_ADMINDIR)
export GL_RC
export REPO_BASE
export GL_BINDIR
export GL_ADMINDIR

View file

@ -48,7 +48,7 @@ main() {
[[ $upgrade == 0 ]] && initial_conf_key
# MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf"
ssh $p_port $user@$host "cd $GL_ADMINDIR; \$PWD/src/gl-compile-conf $quiet"
ssh $p_port $user@$host "cd $GL_ADMINDIR; src/gl-auth-command -e src/gl-compile-conf $quiet"
setup_pta
@ -361,9 +361,9 @@ run_install() {
prompt "installing/upgrading..." "$v_ignore_stuff"
# extract the GL_ADMINDIR, REPO_BASE and GIT_PATH locations
GL_ADMINDIR=$(ssh $p_port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'")
REPO_BASE=$( ssh $p_port $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'")
GIT_PATH=$( ssh $p_port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GIT_PATH'")
GL_ADMINDIR=$(ssh $p_port $user@$host gitolite-install/src/gl-query-rc GL_ADMINDIR)
REPO_BASE=$( ssh $p_port $user@$host gitolite-install/src/gl-query-rc REPO_BASE)
GIT_PATH=$( ssh $p_port $user@$host gitolite-install/src/gl-query-rc GIT_PATH)
# determine if this is an upgrade; we decide based on whether a file
# called $GL_ADMINDIR/conf/gitolite.conf exists on the remote side. We
@ -382,7 +382,7 @@ run_install() {
# MANUAL: still in the "gitolite-install" directory? Good. Run
# "src/gl-install"
ssh $p_port $user@$host "cd gitolite-install; src/gl-install $quiet"
ssh $p_port $user@$host "cd gitolite-install; src/gl-auth-command -e src/gl-install $quiet"
# MANUAL: if you're upgrading, run "src/gl-compile-conf" and you're done!
# -- ignore the rest of this file for the purposes of an upgrade
@ -443,7 +443,7 @@ GIT_WORK_TREE=$GL_ADMINDIR git diff --cached --quiet 2>/dev/null || GIT_WORK_TRE
# properly. The install program does this. So cd back to the
# "gitolite-install" directory and run "src/gl-install"
ssh $p_port $user@$host "cd gitolite-install; src/gl-install $quiet"
ssh $p_port $user@$host "cd gitolite-install; src/gl-auth-command -e src/gl-install $quiet"
# MANUAL: you're done! Log out of the server, come back to your
# workstation, and clone the admin repo using "git clone

View file

@ -5,7 +5,22 @@
use strict;
use warnings;
our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF, $GIT_PATH, $GL_PACKAGE_CONF, $GL_PACKAGE_HOOKS, $GL_PERFLOGT, $REPO_UMASK);
# ----------------------------------------------------------------------------
# find the rc file, then pull the libraries
# ----------------------------------------------------------------------------
BEGIN {
die "ENV GL_RC not set\n" unless $ENV{GL_RC};
die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR};
}
use lib $ENV{GL_BINDIR};
use gitolite_rc;
use gitolite;
# ----------------------------------------------------------------------------
# start...
# ----------------------------------------------------------------------------
# setup quiet mode if asked; please do not use this when running manually
open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q');
@ -25,21 +40,13 @@ sub wrap_mkdir
print "created $dir\n";
}
# the common setup module is in the same directory as this running program is
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
unshift @INC, $bindir;
require gitolite or die "parse gitolite.pm failed\n";
# ask where the rc file is, get it, and "do" it
&where_is_rc();
unless ($ENV{GL_RC}) {
# doesn't exist. Copy it across, tell user to edit it and come back
my $glrc = $ENV{HOME} . "/.gitolite.rc";
if ($GL_PACKAGE_CONF) {
system("cp $GL_PACKAGE_CONF/example.gitolite.rc $glrc");
} else {
system("cp $bindir/../conf/example.gitolite.rc $glrc");
system("cp $ENV{GL_BINDIR}/../conf/example.gitolite.rc $glrc");
}
print "created $glrc\n";
print "please edit it, change the paths if you wish to, and RERUN THIS SCRIPT\n";
@ -69,8 +76,8 @@ for my $dir qw(conf doc keydir logs src hooks hooks/common hooks/gitolite-admin)
if ($GL_PACKAGE_HOOKS) {
system("cp -R -p $GL_PACKAGE_HOOKS $GL_ADMINDIR");
} else {
system("cp -R -p $bindir/../src $bindir/../doc $bindir/../hooks $GL_ADMINDIR");
system("cp $bindir/../conf/VERSION $GL_ADMINDIR/conf");
system("cp -R -p $ENV{GL_BINDIR}/../src $ENV{GL_BINDIR}/../doc $ENV{GL_BINDIR}/../hooks $GL_ADMINDIR");
system("cp $ENV{GL_BINDIR}/../conf/VERSION $GL_ADMINDIR/conf");
}
unless (-f $GL_CONF or $GL_PACKAGE_CONF) {

View file

@ -3,8 +3,13 @@
export GL_BYPASS_UPDATE_HOOK
GL_BYPASS_UPDATE_HOOK=1
export REPO_BASE=`cd $HOME;perl -e 'do ".gitolite.rc"; print $REPO_BASE' `
export REPO_UMASK=`cd $HOME;perl -e 'do ".gitolite.rc"; print $REPO_UMASK' `
get_rc_val() {
${0%/*}/gl-query-rc $1
}
REPO_BASE=$( get_rc_val REPO_BASE)
REPO_UMASK=$(get_rc_val REPO_UMASK)
umask $REPO_UMASK
if echo $SSH_ORIGINAL_COMMAND | egrep git-upload\|git-receive >/dev/null
@ -13,8 +18,8 @@ then
# the (special) admin post-update hook needs these, so we cheat
export GL_ADMINDIR
export GL_BINDIR
GL_ADMINDIR=` cd $HOME;perl -e 'do ".gitolite.rc"; print $GL_ADMINDIR'`
GL_BINDIR=`echo $0 | perl -lpe 's/^/$ENV{PWD}\// unless /^\//; s/\/[^\/]+$//;'`
GL_ADMINDIR=$(get_rc_val GL_ADMINDIR)
GL_BINDIR=$( get_rc_val GL_BINDIR)
SSH_ORIGINAL_COMMAND=`echo $SSH_ORIGINAL_COMMAND | sed -e "s:':'$REPO_BASE/:"`
exec git shell -c "$SSH_ORIGINAL_COMMAND"

View file

@ -6,7 +6,7 @@ ssh -o PasswordAuthentication=no $mirror echo hello-there | grep hello-there >/d
{ echo I cant ssh to $mirror; exit 1; }
cd $HOME
REPO_BASE=` cd $HOME;perl -e 'do ".gitolite.rc"; print $REPO_BASE'`
REPO_BASE=`${0%/*}/gl-query-rc REPO_BASE`
cd $REPO_BASE
ssh $mirror cat \$HOME/.gitolite.rc | expand | egrep '^ *\$GL_SLAVE_MODE *= *1; *$' >/dev/null || {

23
src/gl-query-rc Executable file
View file

@ -0,0 +1,23 @@
#!/usr/bin/perl
# let shell scripts query rc values
# prints out a tab delimited list of all queried values
# just run "gl-query-rc REPO_BASE GL_ADMINDIR" (for example)
use strict;
no strict 'refs';
use warnings;
# find the rc file, then pull the libraries
BEGIN {
# find and set bin dir; same code as in gl-auth-command
$0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ($1 || "$ENV{PWD}/") . $2;
}
use lib $ENV{GL_BINDIR};
require gitolite_rc;
gitolite_rc->import;
our $GL_RC=$ENV{GL_RC};
our $GL_BINDIR=$ENV{GL_BINDIR};
print join("\t", map { $$_ } grep { $$_ } @ARGV) . "\n" if @ARGV;

View file

@ -20,6 +20,10 @@ GL_PACKAGE_CONF=/tmp/share/gitolite/conf
die() { echo "$@"; exit 1; } >&2
get_rc_val() {
${0%/*}/gl-query-rc $1
}
TEMPDIR=$(mktemp -d -t tmp.XXXXXXXXXX)
export TEMPDIR
trap "/bin/rm -rf $TEMPDIR" 0
@ -40,20 +44,24 @@ else
fi
fi
if [ -f $HOME/.gitolite.rc ]
export GL_RC
GL_RC=$(get_rc_val GL_RC 2>/dev/null)
[ -z "$GL_RC" ] && GL_RC=$HOME/.gitolite.rc
if [ -f $GL_RC ]
then
print_rc_vars() {
perl -ne 's/^\s+//; s/[\s=].*//; print if /^\$/;' < $1 | sort
}
print_rc_vars $GL_PACKAGE_CONF/example.gitolite.rc > $TEMPDIR/.newvars
print_rc_vars $HOME/.gitolite.rc > $TEMPDIR/.oldvars
print_rc_vars $GL_RC > $TEMPDIR/.oldvars
comm -23 $TEMPDIR/.newvars $TEMPDIR/.oldvars > $TEMPDIR/.diffvars
if [ -s $TEMPDIR/.diffvars ]
then
cp $GL_PACKAGE_CONF/example.gitolite.rc $HOME/.gitolite.rc.new
echo new version of the rc file saved in $HOME/.gitolite.rc.new
echo
echo please update $HOME/.gitolite.rc manually if you need features
echo please update $GL_RC manually if you need features
echo controlled by any of the following variables:
echo ----
sed -e 's/^/ /' < $TEMPDIR/.diffvars
@ -63,11 +71,11 @@ else
[ -n "$GITOLITE_HTTP_HOME" ] || [ -n "$pubkey_file" ] || die "looks like first run -- I need a pubkey file"
[ -z "$GITOLITE_HTTP_HOME" ] || [ -n "$admin_name" ] || die "looks like first run -- I need an admin name"
cp $GL_PACKAGE_CONF/example.gitolite.rc $HOME/.gitolite.rc
printf "The default settings in the "rc" file ($HOME/.gitolite.rc) are fine for most\n"
cp $GL_PACKAGE_CONF/example.gitolite.rc $GL_RC
printf "The default settings in the "rc" file ($GL_RC) are fine for most\n"
printf "people but if you wish to make any changes, you can do so now.\n\nhit enter..."
read i
${EDITOR:-vi} $HOME/.gitolite.rc
${EDITOR:-vi} $GL_RC
fi
# setup ssh stuff. We break our normal rule that we will not fiddle with
@ -80,16 +88,17 @@ fi
chmod go-w . .ssh .ssh/authorized_keys
)
export GL_BINDIR
export REPO_BASE
export GL_ADMINDIR
GL_BINDIR=$( get_rc_val GL_BINDIR )
REPO_BASE=$( get_rc_val REPO_BASE )
GL_ADMINDIR=$(get_rc_val GL_ADMINDIR)
# now we get to gitolite itself
gl-install -q
get_rc_val() {
perl -e "do '$HOME/.gitolite.rc'; print $1"
}
GL_ADMINDIR=$(get_rc_val '$GL_ADMINDIR')
REPO_BASE=$( get_rc_val '$REPO_BASE' )
[ -f $GL_ADMINDIR/conf/gitolite.conf ] || {
cat <<EOF | cut -c9- > $GL_ADMINDIR/conf/gitolite.conf
repo gitolite-admin

View file

@ -1,43 +1,51 @@
#!/usr/bin/perl -w
# shim program
# documentation for this program is right here, please read
# arg-1: keydir
# IMPORTANT NOTES:
# - this program MUST be placed in the same directory as the rest of the
# programs that come with gitolite
# - this program MUST be run by supplying its full path!
# BACKGROUND/PURPOSE:
# - an external program populates "keydir" with *all* keys and then
# calls us, giving "keydir" as arg-1
# calls this program, giving "keydir" as arg-1
# - we then call gitolite.pm's "setup_authkeys" function to do its thing
# IMPLEMENTATION NOTE: make sure this is in the same directory as
# "gitolite.pm" and all the rest of "src/".
# arg-1: keydir
# DISCUSSION:
#
# For now, we will assume *all* the keys are in the keydir passed. The
# setup_authkeys routine factored out from the old gl-compile-conf is
# not setup to take a partial set of keys and create the
# ~/.ssh/authorized_keys file.
# setup_authkeys routine factored out from the old gl-compile-conf is not
# setup to take a partial set of keys and create the ~/.ssh/authorized_keys
# file.
#
# Also, there are issues to do with *deleted* keys that need to be taken
# care of.
# Also, there are issues to do with *deleted* keys that need to be taken care
# of.
#
# All in all, unless it is shown to be quite inefficient, I'd much
# prefer processing *all* keys each time there is a change.
# All in all, unless it is shown to be quite inefficient, I'd much prefer
# processing *all* keys each time there is a change.
our ($GL_PERFLOGT);
use strict;
use warnings;
# setup
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//;
unshift @INC, $bindir;
require gitolite or die "parse gitolite.pm failed\n";
use FindBin;
BEGIN { $ENV{GL_BINDIR} = $FindBin::Bin; }
# prevent newbie from running it accidentally and clobbering his authkeys
# file!
if (@ARGV and $ARGV[0] eq '-batch') {
shift;
} else {
use lib $ENV{GL_BINDIR};
use gitolite_rc;
use gitolite;
use Getopt::Long;
my $batch = 0;
GetOptions('batch' => \$batch);
# prevent newbie from running it accidentally and clobbering his authkeys file!
unless ($batch) {
print STDERR "
This is a cronnable, batchable, program to rewrite ~/.ssh/authorized_keys
using public keys in a given directory.
@ -54,4 +62,4 @@ if (@ARGV and $ARGV[0] eq '-batch') {
my $keydir = shift or die "I need a directory name\n";
-d $keydir or die "$keydir should be a directory\n";
&setup_authkeys($bindir, $keydir);
setup_authkeys($keydir);

View file

@ -9,22 +9,32 @@
use strict;
use warnings;
# ----------------------------------------------------------------------------
# find the rc file, then pull the libraries
# ----------------------------------------------------------------------------
# see notes on this code in gl-auth-command
BEGIN {
# find and set bin dir
$0 =~ m|^(/)?(.*)/| and $ENV{GL_BINDIR} = ($1 || "$ENV{PWD}/") . $2;
}
use lib $ENV{GL_BINDIR};
use gitolite_rc;
use gitolite_env;
use gitolite qw(log_it);
use Time::HiRes qw(gettimeofday tv_interval);
our ($GL_PERFLOGT);
# ----------------------------------------------------------------------------
# start...
# ----------------------------------------------------------------------------
# rc file
do "$ENV{HOME}/.gitolite.rc";
# this file is always in a fixed place; code in the main gitolite that
# seems to indicate it is not, is obsolete and needs to be fixed.
# the common setup module is in the same directory as this running program is
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//;
unshift @INC, $bindir;
require gitolite or die "parse gitolite.pm failed\n";
# ---------------------------------------------------------------
my $starttime = [gettimeofday];
@ -36,6 +46,6 @@ $ENV{GL_USER} = shift;
my $elapsedtime = tv_interval($starttime);
$ENV{GL_LOG} = &get_logfilename($GL_PERFLOGT);
$ENV{GL_LOG} = get_logfilename($GL_PERFLOGT);
# log_it logs to $ENV{GL_LOG}
&log_it("", "$elapsedtime\trc=$returncode");
log_it("", "$elapsedtime\trc=$returncode");

View file

@ -14,8 +14,9 @@
# current sub-commands:
# (1) REPLACE THE OLD $SHELL_USERS MECHANISM
#
# $0 shell-add foo.pub
#
# adds the pubkey in foo.pub into the authkeys file with "-s" argument (shell
# access) and user "foo". The line will be added *before* the "# gitolite
# start" section, so that a gitolite-admin push will not affect it.
@ -45,12 +46,12 @@ then
# side, it's not likely to change anytime soon!
AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding"
bindir=`echo $0 | perl -lpe 's/^/$ENV{PWD}\// unless /^\//; s/\/[^\/]+$//;'`
GL_BINDIR=`${0%/*}/gl-query-rc GL_BINDIR`
pubkey_file=$2
user=`basename $pubkey_file .pub`
authline="command=\"$bindir/gl-auth-command -s $user\",$AUTH_OPTIONS `cat $pubkey_file`";
authline="command=\"$GL_BINDIR/gl-auth-command -s $user\",$AUTH_OPTIONS `cat $pubkey_file`";
authkeys=$HOME/.ssh/authorized_keys

View file

@ -6,8 +6,8 @@ our (%users, %linenos);
my $thisbin = $0;
$thisbin = "$ENV{PWD}/$thisbin" unless $thisbin =~ /^\//;
&usage unless $ARGV[0] and -f $ARGV[0];
my @authlines = &filelines($ARGV[0]);
usage() unless $ARGV[0] and -f $ARGV[0];
my @authlines = filelines($ARGV[0]);
my $lineno = 0;
for (@authlines)
{
@ -36,7 +36,7 @@ print "\n";
my @pubkeys = glob("*.pub");
die "no *.pub files here\n" unless @pubkeys;
for my $pub (@pubkeys) {
my @lines = &filelines($pub);
my @lines = filelines($pub);
die "$pub has more than one line\n" if @lines > 1;
die "$pub does not start with ssh-rsa or ssh-dss\n" unless $lines[0] =~ /^(?:ssh-rsa|ssh-dss) (\S+)/;
my $key = $1;

View file

@ -71,6 +71,15 @@ In this document:
# or
./test-driver.sh t51
* you can also run them through "prove", although to make it work easier
with prove, I ended up making the "subtest" numbers be the actual test
numbers, making it look like I have over 2000 tests, when in reality I
have about 600:
prove ./test-driver.sh
# or
prove ./test-driver.sh :: t51
<a name="instructions_for_adding_new_tests"></a>
### instructions for adding new tests

View file

@ -26,8 +26,8 @@ name "u1 rsync to frob"
cd ~/gitolite-admin
runlocal rsync -avP conf u1:frob
expect conf/gitolite.conf
expect 386.*100%
expect "total size is 386"
expect 100%
expect "total size is"
runlocal find /tmp/rsyncbase -type f
expect /tmp/rsyncbase/frob/conf/gitolite.conf
@ -42,8 +42,8 @@ name "u2 rsync to nitz"
cd ~/gitolite-admin
runlocal rsync -avP conf u2:nitz
expect conf/gitolite.conf
expect 386.*100%
expect "total size is 386"
expect 100%
expect "total size is"
runlocal find /tmp/rsyncbase -type f
expect /tmp/rsyncbase/nitz/conf/gitolite.conf
@ -55,8 +55,8 @@ expect "W NAME/spl u2 DENIED by NAME/spl"
name "u1 rsync to spl"
cd ~/gitolite-admin
runlocal rsync -avP conf u1:spl
expect 386.*100%
expect "total size is 386"
expect 100%
expect "total size is"
name "u2 rsync from spl"
cd ~/td
@ -66,14 +66,14 @@ expect "R NAME/spl u2 DENIED by NAME/spl"
name "u1 rsync from spl"
cd ~/td
runlocal rsync -avP u1:spl splhere
expect 386.*100%
expect "total size is 386"
expect 100%
expect "total size is"
name "u3 rsync to foo"
cd ~/gitolite-admin
runlocal rsync -avP conf u3:foo/
expect 386.*100%
expect "total size is 386"
expect 100%
expect "total size is"
name "u3 rsync to bar"
cd ~/gitolite-admin

View file

@ -4,7 +4,6 @@
# documentation
testnum=0
subtests=0
# remote local command
runlocal() { "$@" > ~/1 2> ~/2; }
@ -28,22 +27,22 @@ taillog() { ssh gitolite-test@localhost tail $1 .gitolite/logs/gitolite-????-??.
hl() { # highlight function
normal=`tput sgr0`
red=`tput sgr0; tput setaf 1; tput bold`
echo >&2
if [[ -n $1 ]]
then
echo $red"$@"$normal
echo $red"$@"$normal >&2
else
echo $red
echo $red >&2
cat
echo $normal
echo $normal >&2
fi
}
pause() { echo pausing, "$@"\; hit enter or ctrl-c...; read; }
capture() { cf=$1; shift; "$@" >& $TESTDIR/$cf; }
editrc() {
scp gitolite-test@localhost:.gitolite.rc ~/junk >/dev/null
perl -pi -e "print STDERR if not /^#/ and /$1\b/ and s/=.*/= $2;/" ~/junk
perl -pi -e "print STDERR if not /^#/ and /$1\b/ and s/=.*/= $2;/" ~/junk 2> >(sed -e 's/^/# /')
scp ~/junk gitolite-test@localhost:.gitolite.rc >/dev/null
}
@ -80,80 +79,64 @@ mdc()
) >~/1 2>~/2
}
# flush result of last test when next one comes along
testdone() {
[[ $subtests > 1 ]] && TESTNAME="($subtests) $TESTNAME"
echo -e $testnum\\t$TESTNAME
}
# set test name/desc
name() {
if [[ -n $TESTNAME ]]
then
export TESTNAME="$*"
if [[ $TESTNAME != INTERNAL ]]
then
(( testnum++ ))
testdone
echo '#' "$*"
fi
subtests=0
fi
export TESTNAME="$*"
}
ok() {
(( testnum++ ))
echo 'ok' "($testnum) $*"
}
notok() {
echo ----------
head -999 ~/1 ~/2 | sed -e 's/^/ /'
(( testnum++ ))
echo 'not ok' "($testnum) $*"
}
expect_filesame() {
if cmp ~/1 "$1"
then
(( subtests++ ))
ok
else
echo files ~/1 and "$1" are different
echo '*** ABORTING ***'
exit 1
notok files ~/1 and "$1" are different
fi
}
die() {
echo '***** AAAAARRRGGH! *****'
echo ${BASH_LINENO[1]} ${BASH_SOURCE[2]}
read
cd $TESTDIR
vim +${BASH_LINENO[1]} '+r !head ~/1 ~/2 /dev/null' ${BASH_SOURCE[2]}
echo '***** AAAAARRRGGH! *****' >&2
echo ${BASH_LINENO[1]} ${BASH_SOURCE[2]} >&2
echo "vim +${BASH_LINENO[1]} \'+r !head ~/1 ~/2 /dev/null\' ${BASH_SOURCE[2]}" >&2
exit 1
}
expect() {
if cat ~/1 ~/2 | grep "$1" >/dev/null
then
(( subtests++ ))
ok
else
notok
echo ----------
echo " expecting: $1"
echo ----------
die $TESTNAME
exit 1
notok "expecting: $1, got:"
cat ~/1 ~/2|sed -e 's/^/# /'
fi
}
notexpect() {
if cat ~/1 ~/2 | grep "$1" >/dev/null
then
notok
echo "NOT expecting: $1"
echo ----------
die $TESTNAME
exit 1
notok "NOT expecting: $1, got:"
cat ~/1 ~/2|sed -e 's/^/# /'
else
(( subtests++ ))
ok
fi
}
print_summary() {
echo -e "==========\n$testnum tests succeeded"
echo 1..$testnum
}
expect_push_ok() {