#!/usr/bin/perl

# READ ALL INSTRUCTIONS **AND** SOURCE CODE BEFORE DEPLOYING.

# run arbitrary git commands on the server

# ----

# WARNING: HIGHLY INFLAMMABLE.  FISSILE MATERIAL, RADIATION HAZARD.  HANDLE
# WITH CARE.  DO NOT REMOVE MANUFACTURER LABEL.  NOT TO BE USED WHILE DRIVING
# OR UNDER THE INFLUENCE OF ALCOHOL.  PATIENTS WITH HEART PROBLEMS MUST SEE
# THEIR CARDIOLOGIST BEFORE USING.

# ----

# ok, warnings done, here's the saner description.
#
# This ADC lets you run arbirtrary git commands on any repo on the server.
# The first argument will be the repo name, the second and subsequent
# arguments will be the rest of the git command.  For example, to run `git
# describe --tags` on repo `foo`, you would run:
#
#   ssh git@server git foo describe --tags
#
# If that looks weird to you, you can use
#
#   ssh git@server git --repo=foo describe --tags
#
# (the position remains the same: between 'git' and '<command>')

# SECURITY AND SAFETY NOTES:
#
# - ADC arguments are checked (in `sub try_adc`) to fit `ADC_CMD_ARGS_PATT`
#   and the only special characters allowed by that pattern are ".", "_", "@",
#   "/", "+", ":", and "-".  Thus, *this* adc does not check arguments
#   anymore.  ANY RISK IN THIS LAXITY IS YOURS, NOT MINE, although I believe
#   it is safe enough.
#
# - Most commands don't make sense to allow, even among those that do not
#   require a work-tree.  Avoid commands that can be done using normal git
#   remote access (ls-remote, clone, archive, push, etc).  Also, avoid
#   commands that *write* to the repo if possible, or at least think/test
#   thoroughly before enabling them.
#
# - You have to deal with issues like stdin/out, output files created etc.,
#   which is another reason to avoid most of the more complex commands.
#
# - Do not enable prune, gc, etc., if your repos are on NFS/CIFS/etc.  See
#   http://permalink.gmane.org/gmane.comp.version-control.git/122670 for why.
#
# - The list of commands allowed to be executed, and the permissions required
#   to do so, are defined here.  Feel free to uncomment any of this to make
#   things more relaxed.  If you add new ones, note that the permissions can
#   only be 'R', 'W', or 'A'.  The meanings of R and W are obvious; "A" means
#   the user must have write access to the *gitolite-admin* repo to run this
#   command -- yeah that's a nice twist innit? ;-)

my %GIT_COMMANDS = (
#   annotate         =>  'R',
#   blame            =>  'R',
    'count-objects'  =>  'R',
    describe         =>  'R',
#   diff             =>  'R',
#   'fast-export'    =>  'R',
#   grep             =>  'R',
#   log              =>  'R',
#   shortlog         =>  'R',
#   'show-branch'    =>  'R',
#   show             =>  'R',
#   whatchanged      =>  'R',

#   config           =>  'A',   # I strongly discourage un-commenting this
#   fsck             =>  'W',   # write access required
#   gc               =>  'W',   # write access required
#   prune            =>  'A',   # admin access required
#   repack           =>  'A',   # admin access required
);

# preliminary stuff; indented just to visually get it out of the way

    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;

    my $no_help = "this command is too dangerous to just show a help message; we don't want anyone\nrunning it without reading the source and understanding the implications!\n";

# get the repo name
my $repo = shift or die $no_help;
$repo =~ s/^--repo=//;
$repo =~ s/\.git$//;
# get the command
my $cmd = shift or die $no_help;

# is it a valid command at all?
exists $GIT_COMMANDS{$cmd} or die "invalid git command\n";

# check access
my $aa = $GIT_COMMANDS{$cmd};   # aa == attempted access
if ($aa eq 'A') {
    my ($perm, $creator) = check_access('gitolite-admin');
    $perm =~ /W/ or die "no admin access\n";
} else {
    my ($perm, $creator) = check_access($repo);
    $perm =~ /$aa/ or die "no $aa access to $repo\n";
}

# cd to the repo dir
chdir("$ENV{GL_REPO_BASE_ABS}/$repo.git") or die "chdir failed: $!\n";

# remove or comment the below line to signify you have read and understood all this
die $no_help;

# now run the git command... fingers crossed

unshift @ARGV, "git", $cmd;
print STDERR "+ ", join(" ", @ARGV), "\n";
exec @ARGV;