Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Mass commit of stock ADCs

  • Loading branch information...
commit 2bea1b9565b5a2aa0c0aec5f0e6bad1e02e29394 1 parent 17b48da
@adrienthebo authored
Showing with 2,906 additions and 0 deletions.
  1. +52 −0 files/adc/README-adc.mkd
  2. +56 −0 files/adc/able
  3. +132 −0 files/adc/adc.common-functions
  4. +84 −0 files/adc/delete-branch
  5. +45 −0 files/adc/fork
  6. +52 −0 files/adc/get-rights-and-owner.in-perl
  7. +23 −0 files/adc/getdesc
  8. +123 −0 files/adc/git
  9. +63 −0 files/adc/git-annex-shell
  10. +101 −0 files/adc/gl-reflog
  11. +63 −0 files/adc/help
  12. +30 −0 files/adc/htpasswd
  13. +470 −0 files/adc/hub
  14. +184 −0 files/adc/hub.mkd
  15. +13 −0 files/adc/list-trash
  16. +12 −0 files/adc/lock
  17. +115 −0 files/adc/perms
  18. +77 −0 files/adc/pygitolite.py
  19. +55 −0 files/adc/repo-deletion.mkd
  20. +16 −0 files/adc/restore
  21. +10 −0 files/adc/restrict-admin
  22. +40 −0 files/adc/rm
  23. +4 −0 files/adc/rmrepo
  24. +76 −0 files/adc/rsync
  25. +150 −0 files/adc/s3backup
  26. +25 −0 files/adc/setdesc
  27. +275 −0 files/adc/sskm
  28. +237 −0 files/adc/sskm.mkd
  29. +55 −0 files/adc/su-expand
  30. +32 −0 files/adc/su-getperms
  31. +1 −0  files/adc/su-setperms
  32. +23 −0 files/adc/sudo
  33. +20 −0 files/adc/svnserve
  34. +35 −0 files/adc/symbolic-ref
  35. +29 −0 files/adc/trash
  36. +12 −0 files/adc/unlock
  37. +84 −0 files/adc/watch
  38. +32 −0 files/adc/who-pushed
View
52 files/adc/README-adc.mkd
@@ -0,0 +1,52 @@
+# F=shipped_ADCs brief descriptions of the shipped ADCs (admin-defined commands)
+
+(...with pointers to further information where needed)
+
+----
+
+**able**: enable/disable push access temporarily (such as for taking backups
+or other admin chores); details [here][able]. This ADC is meant only for
+admins.
+
+**delete-branch**: allow someone to delete a branch that they themselves
+created. (i.e., when the user had RWC, but not RWCD, permissions). Details on
+this ADC are [here][dbsha]; details on RWC/RWD/RWCD etc are [here][rwcd].
+
+[dbsha]: https://github.com/sitaramc/gitolite/commit/89b68bf5ca99508caaa768c60ce910d7e0a29ccf
+
+**fork**: Think of it as a server-side clone; details [here][fork].
+
+**get-rights-and-owner.in-perl**: Most of the ADCs are in shell, so this is a
+sample of how to write an ADC in perl.
+
+**git-annex-shell**: allows git-annex to store and retrieve annexed file content in
+repositories. To use, install in `$GL_ADC_PATH/ua/git-annex-shell`
+
+**gl-reflog**: show a fake "reflog" from the server, and allow recovery from
+deleted branches and bad force pushes; details in source.
+
+**help**: not all shipped ADCs may be enabled by the site admin. Conversely
+the admin may create and install his own ADCs that dont ship with gitolite
+itself. This ADC displays site-local help, if the site admin enabled it.
+
+**hub**: allow "pull requests" a la github; details [here][hub].
+
+**rm**, **lock**, and **unlock**:<br>
+**trash**, **list-trash**, and **restore**:
+
+> two families of repo deletion commands; details [here][wild_repodel]
+
+**sudo**: allow admin to run ADCs on behalf of a user. Useful in support
+situations I guess. Details in source.
+
+Note: the Unix "sudo" and "su" programs are most often used to acquire
+*higher* privileges, although they're actually designed to go the other way
+also. In gitolite we do not do the former, only the latter (i.e., a normal
+gitolite user cannot do admin-stuff using this ADC).
+
+**su-expand**, **su-getperms**, **su-setperms**: as above, but for the
+internal commands 'expand', 'getperms', and 'setperms'. (These commands are
+not ADCs so you cannot simply use the 'sudo' ADC described above).
+
+**who-pushed**: find the last person to push a given commit; details in
+source.
View
56 files/adc/able
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+. $(dirname $0)/adc.common-functions
+
+is_admin || die "just *what* are you trying to pull, young man?"
+
+op=$1
+shift
+
+locs=
+while [ -n "$1" ]
+do
+ case $1 in
+ '@all' )
+ locs="$locs $HOME"
+ ;;
+ * )
+ loc="$GL_REPO_BASE_ABS/$1.git"
+ [ -d $loc ] && locs="$locs $GL_REPO_BASE_ABS/$1.git"
+ [ -d $loc ] || echo "ignoring $1..."
+ ;;
+ esac
+ shift
+done
+
+[ -z "$locs" ] && die "give me '@all' or some reponame"
+
+case $op in
+ en|enable )
+ for l in $locs
+ do
+ rm -fv $l/.gitolite.down
+ done
+ ;;
+ dis|disable )
+
+ TEMPDIR=$(mktemp -d -t tmp.XXXXXXXXXX)
+ export TEMPDIR
+ trap "/bin/rm -rf $TEMPDIR" 0
+
+ echo 'type the message to be shown to users when they try to push; end with Ctrl-D:'
+ echo > $TEMPDIR/msg
+ cat >> $TEMPDIR/msg
+ echo disabling following locations with message:
+ cat $TEMPDIR/msg
+ echo
+ for l in $locs
+ do
+ cat $TEMPDIR/msg > $l/.gitolite.down
+ echo $l
+ done
+ ;;
+ * )
+ die "argument 1 must be 'en' or 'dis'"
+ ;;
+esac
View
132 files/adc/adc.common-functions
@@ -0,0 +1,132 @@
+#!/bin/sh
+
+# please make sure this file is NOT chmod +x
+
+# this file contains settings for all ADCs at the top, then functions that you
+# can call from shell scripts. Other files in this directory have examples.
+
+# all uses require you to "source" this file, like so:
+
+# # at the top of your ADC
+# . $(dirname $0)/adc.common-functions
+
+# then you use one of the following functions, like so:
+
+# can_create reponame || die "you can't create reponame"
+# can_write reponame || die "you can't write reponame"
+# can_read reponame || die "you can't read reponame"
+# is_admin || die "you're not an admin"
+
+# IMPORTANT NOTE: all the can_* functions set $repo to the normalised reponame
+# (i.e., with '.git' extension removed if it was supplied).
+
+# ------------------------------------------------------------------------------
+
+# settings for various ADCs, collected in one place for ease of keeping local
+# settings intact during upgrades (you only have to worry about this file
+# now). Documentation for the variables, however, is in the respective ADC
+
+# settings for 'rm' ADC
+ARE_YOU_SURE=1
+USE_LOCK_UNLOCK=1
+
+# settings for 'trash' ADC
+TRASH_CAN=$GL_REPO_BASE_ABS/deleted
+TRASH_SUFFIX=`date +%Y-%m-%d_%H:%M:%S`
+
+# settings for 'hub' ADC
+BASE_FETCH_URL="git://gl.example.com"
+GL_FORKED_FROM="gl-forked-from"
+ # KDE may set this to kde-cloned-from for historical reasons
+
+# Change to 1 to make -list the default action for the 'help' command
+HELP_LIST_DEFAULT=0
+
+# name of "admin" group (see is_admin() below before uncommenting)
+# ADMIN_GROUPNAME=admins
+
+# ------------------------------------------------------------------------------
+
+die() { echo "$@"; exit 1; }
+
+# test an option value more concisely
+opt() {
+ [ "$1" = "1" ] && return 0
+ return 1
+}
+
+valid_owned_repo() {
+ # check that an arg passed is a valid repo and the current user owns it
+ [ -z "$1" ] && die need a repo name
+ get_rights_and_owner $1
+ [ "$owner" = "$GL_USER" ] || die "$repo does not exist or is not yours!"
+
+ # and we sneak this in too, quietly :)
+ cd $GL_REPO_BASE_ABS
+}
+
+# NOTE: this also sets $repo to the normalised (without .git suffix) reponame
+get_rights_and_owner() {
+ local ans
+ repo=${1%.git}
+ ans=$(perl -I$GL_BINDIR -Mgitolite -e "cli_repo_rights('"$repo"')")
+
+ # set shell variables as needed
+ owner=${ans#* }
+ rights=${ans% *}
+ echo $rights | grep C >/dev/null 2>&1 && perm_create=yes || perm_create=
+ echo $rights | grep R >/dev/null 2>&1 && perm_read=yes || perm_read=
+ echo $rights | grep W >/dev/null 2>&1 && perm_write=yes || perm_write=
+}
+
+can_create() {
+ get_rights_and_owner ${1%.git}
+ [ -z "$perm_create" ] && return 1
+ return 0
+}
+
+can_write() {
+ get_rights_and_owner ${1%.git}
+ [ -z "$perm_write" ] && return 1
+ return 0
+}
+
+can_read() {
+ get_rights_and_owner ${1%.git}
+ [ -z "$perm_read" ] && return 1
+ return 0
+}
+
+# ------------------------------------------------------------------------------
+
+# check if current user is an admin
+is_admin() {
+ # there are two ways to check if someone is an admin. The default (if
+ # ADMIN_GROUPNAME is not defined) is to check if they have write access to
+ # the admin repo
+
+ if [ -z "$ADMIN_GROUPNAME" ]
+ then
+ can_write gitolite-admin || return 1
+ return 0
+ fi
+
+ # the alternative way is to check membership in $ADMIN_GROUPNAME; please
+ # remember this method requires GL_BIG_CONFIG to be set
+
+ # TODO, pending the code to allow an external query of a user's "group"
+ # affiliations
+ in_group $ADMIN_GROUPNAME
+}
+
+# ------------------------------------------------------------------------------
+
+grouplist() {
+ perl -I$GL_BINDIR -Mgitolite -e "cli_grouplist()"
+}
+
+in_group() {
+ local g=$1
+ grouplist | egrep "(^| )$g( |$)" >/dev/null && return 0
+ return 1
+}
View
84 files/adc/delete-branch
@@ -0,0 +1,84 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+# allow a user to delete a ref if the last create of the ref was done by the
+# same user, *and* it was done within a certain time limie
+
+# change this to suit your needs
+my $oldest = 60*60*24*7; # in seconds
+
+# use a generic error message to avoid information leak
+my $error = "didn't find repo/ref, or the ref is too old, or you did not create it\n";
+
+# ----
+
+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;
+
+# arg check
+die "need two arguments, a reponame and a refname\n" unless @ARGV == 2;
+# get the repo name
+my $repo = shift;
+$repo =~ s/\.git$//;
+# get the ref name to be deleted, and allow the same convenience shortcut
+# (prefix "refs/heads/" if it doesn't start with "refs/") as in the main
+# config file
+my $ref = shift;
+$ref =~ m(^refs/) or $ref =~ s(^)(refs/heads/);
+
+# XXX WARNING: we do not do any access control checking -- we just go by the
+# fact that if *you* created a branch within the last $limit seconds (default
+# value is 1 week), you are allowed to delete the branch.
+
+# find the earliest log entry we're willing to look at
+my $limit = `date -d '$oldest seconds ago' '+%F.%T'`;
+ # NOTE: this is the format that gitolite uses in its log entries (see sub
+ # 'get_logfilename in one of the pm files). The logic also depends on the
+ # fact that this is sortable, because we read backwards and stop when we
+ # reach something older than $limit
+chomp($limit);
+
+# find the last 2 log files; here also we depend on the fact that the file
+# *names* are time ordered when sorted
+my ($lf1, $lf2) = reverse sort glob("$ENV{GL_ADMINDIR}/logs/gitolite*log");
+
+my $found = 0;
+my($ts, $user, $ip, $cmd, $op, $oldsha, $newsha, $logrepo, $logref, $refrule);
+for my $lf ($lf1, $lf2) {
+ next unless $lf;
+ open(LF, "-|", "tac", $lf) or die "tac $lf failed: $!\n";
+ while (<LF>) {
+ ($ts, $user, $ip, $cmd, $op, $oldsha, $newsha, $logrepo, $logref, $refrule) = split /\t/;
+ next unless $refrule;
+ if ($ts le $limit) {
+ # we don't look at entries earlier than this
+ $found = -1;
+ last;
+ }
+ if ($op eq 'C' and $oldsha =~ /^0+$/ and $logrepo eq $repo and $logref eq $ref) {
+ # creation record found; no need to look at any more entries
+ $found = 1;
+ last;
+ }
+ }
+ last if $found;
+}
+# check user in creation record to make sure it is the same one
+if ($found == 1 and $user eq $ENV{GL_USER}) {
+ chdir("$ENV{GL_REPO_BASE_ABS}/$repo.git") or die "chdir $ENV{GL_REPO_BASE_ABS}/$repo.git failed: $!\n";
+ system("git", "update-ref", "-d", $ref, $newsha) and die "ref deletion failed\n";
+ warn "deleted $ref from $repo (created on $ts)\n";
+ # NOTE: we use warn so this gets into the log in some way; perhaps
+ # later we can adjust the format to more closely resemble a normal
+ # remote delete operation
+ exit 0;
+}
+
+print STDERR $error;
+exit 1;
View
45 files/adc/fork
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+. $(dirname $0)/adc.common-functions
+
+[ -z "$GL_RC" ] && die "ENV GL_RC not set"
+[ -z "$2" ] && die "Usage: fork source_repo target_repo"
+
+# all the can_* functions set $repo
+can_read $1 || die "no read permissions on $repo"
+from=$repo
+
+can_create $2 || die "no create permissions on $repo"
+to=$repo
+
+# clone $from to $to
+git clone --bare -l $GL_REPO_BASE_ABS/$from.git $GL_REPO_BASE_ABS/$to.git
+[ $? -ne 0 ] && exit 1
+
+echo "$from forked to $to"
+
+# fix up creator, gitweb owner, and hooks
+cd $GL_REPO_BASE_ABS/$to.git
+echo $GL_USER > gl-creater
+git config gitweb.owner "$GL_USER"
+( $GL_BINDIR/gl-query-rc GL_WILDREPOS_DEFPERMS ) |
+ SSH_ORIGINAL_COMMAND="setperms $to" $GL_BINDIR/gl-auth-command $GL_USER
+
+# symlink hooks
+shopt -s nullglob
+# the order is important; "package" hooks must override same-named "user" hooks
+for i in `$GL_BINDIR/gl-query-rc GL_ADMINDIR`/hooks/common/* \
+ `$GL_BINDIR/gl-query-rc GL_PACKAGE_HOOKS `/common/*
+do
+ ln -sf $i $GL_REPO_BASE_ABS/$to.git/hooks
+done
+
+if [ -n "$GL_WILDREPOS_DEFPERMS" ]; then
+ echo "$GL_WILDREPOS_DEFPERMS" > gl-perms
+fi
+
+echo "$from" > gl-forked-from
+
+# run gitolite's post-init hook if you can (hook code expects GL_REPO to be set)
+export GL_REPO; GL_REPO="$to"
+[ -x hooks/gl-post-init ] && hooks/gl-post-init
View
52 files/adc/get-rights-and-owner.in-perl
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+
+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;
+$repo =~ s/\.git$//;
+# IMPORTANT NOTE: to do any of this inside a hook, you should just use
+# $ENV{GL_REPO}, since it's guaranteed to be set to the right value
+
+
+
+# to do a "level 1" check (repo level -- not branch level), do this:
+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
+# the presence of "R" or "W" and be done:
+# _____R___W_
+# _____R_____
+# ___________
+
+# The second value is "<gitolite>" for a normal repo, an actual username for
+# a wildrepo, or "<notfound>" for a non-existent repo.
+
+
+
+# to do a "level 2" check (branches), do something like this
+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.
+
+# the return value may look like this:
+# refs/.*
+# or perhaps this, if you were denied
+# DENIED by fallthru
+
+# NOTE: do NOT pass "R" as the 3rd argument. It will seem to work because
+# you're merely testing the permissions in this code, but an *actual* "git
+# fetch" for even a DENIED ref will succeed if the user has read access to at
+# least one branch. This is because the information on what ref is being read
+# is not made available externally in any useful way (the way the "update"
+# hook gets its arguments when a push happens).
View
23 files/adc/getdesc
@@ -0,0 +1,23 @@
+#!/usr/bin/perl
+
+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 $repo = shift;
+die "need a reponame\n" unless $repo;
+
+my $ret = check_access($repo, 'refs/heads/master', '+', 1);
+
+die "sorry you don't have rights to do this\n" if $ret =~ /DENIED/;
+
+wrap_chdir($ENV{GL_REPO_BASE_ABS});
+wrap_chdir("$repo.git");
+
+print slurp("description") if -f "description";
View
123 files/adc/git
@@ -0,0 +1,123 @@
+#!/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;
View
63 files/adc/git-annex-shell
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+# This ADC requires unrestricted arguments, so you need to
+# install it into $GL_ADC_PATH/ua/git-annex-shell, instead of
+# directly into $GL_ADC_PATH/
+#
+# This requires git-annex version 20111016 or newer. Older versions won't
+# be secure.
+
+use strict;
+use warnings;
+
+# pull in modules we need
+BEGIN {
+ 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};
+}
+use gitolite_rc;
+use gitolite;
+
+# ignore @ARGV and look at the original unmodified command
+my $cmd=$ENV{SSH_ORIGINAL_COMMAND};
+
+# Expect commands like:
+# git-annex-shell 'configlist' '/~/repo'
+# git-annex-shell 'sendkey' '/~/repo' 'key'
+# The parameters are always single quoted, and the repo path is always
+# the second parameter.
+# Further parameters are not validated here (see below).
+die "bad git-annex-shell command: $cmd"
+ unless $cmd =~ m#^(git-annex-shell '\w+' ')/\~/([0-9a-zA-Z][0-9a-zA-Z._\@/+-]*)('( .*|))$#;
+my $start = $1;
+my $repo = $2;
+my $end = $3;
+die "I dont like some of the characters in $repo\n" unless $repo =~ $REPONAME_PATT;
+die "I dont like absolute paths in $cmd\n" if $repo =~ /^\//;
+die "I dont like '..' paths in $cmd\n" if $repo =~ /\.\./;
+
+# Modify $cmd, fixing up the path to the repo to include REPO_BASE.
+my $newcmd="$start$REPO_BASE/$repo$end";
+
+# Rather than keeping track of which git-annex-shell commands
+# require write access and which are readonly, we tell it
+# when readonly access is needed.
+my ($perm, $creator) = check_access($repo);
+if ($perm =~ /W/) {
+}
+elsif ($perm =~ /R/) {
+ $ENV{GIT_ANNEX_SHELL_READONLY}=1;
+}
+else {
+ die "$perm $repo $ENV{GL_USER} DENIED\n";
+}
+# Further limit git-annex-shell to safe commands (avoid it passing
+# unknown commands on to git-shell)
+$ENV{GIT_ANNEX_SHELL_LIMITED}=1;
+
+# Note that $newcmd does *not* get evaluated by the unix shell.
+# Instead it is passed as a single parameter to git-annex-shell for
+# it to parse and handle the command. This is why we do not need to
+# fully validate $cmd above.
+log_it();
+exec "git-annex-shell", "-c", $newcmd;
View
101 files/adc/gl-reflog
@@ -0,0 +1,101 @@
+#!/usr/bin/perl -w
+
+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
+
+# --------------------
+
+# WARNING
+# - heavily dependent on the gitolite log file format (duh!)
+# - cannot recover if some other commits were made after the force push
+
+sub usage {
+ print STDERR <<'EOF';
+ USAGE
+ ssh git@server gl-reflog show r1 refs/heads/b1
+ # shows last 10 updates to branch b1 in repo r1
+ ssh git@server gl-reflog show r1 refs/heads/b1 20
+ # shows last 20 entries...
+ ssh git@server gl-reflog recover r1 refs/heads/b1
+ # recovers the last update to b1 in r1 if it was a "+"
+EOF
+ exit 1;
+}
+
+usage unless (@ARGV >= 3);
+
+# NOTES
+# - the verb "recover" is used because this is expected to be used most often
+# to recover deleted branches. Plus there's enough confusion in git land
+# caused by "reset" and "revert" I thought I should add my bit to it ;-)
+# - git's internal reflog is NOT recovered, even if you recover the branch.
+# I'm good but not *that* good ;-)
+# - since this program produces a log entry that satisfies it's own criteria,
+# it acts as a "toggle" for its own action for rewinds (but not for deletes)
+
+my($cmd, $repo, $ref, $limit) = @ARGV;
+$limit ||= 10;
+
+unshift @INC, $ENV{GL_BINDIR};
+require gitolite or die "parse gitolite.pm failed\n";
+gitolite->import;
+
+my ($perm, $creator) = check_access($repo);
+die "you don't have read access to $repo\n" unless $perm =~ /R/;
+
+my @logfiles = sort glob("$ENV{GL_ADMINDIR}/logs/*");
+
+# TODO figure out how to avoid reading *all* the log files when you really
+# only need the last few
+
+our @loglines;
+{
+ my @f;
+ local(@ARGV) = @logfiles;
+ while (<>) {
+ chomp;
+ @f = split /\t/;
+ # field 2 is the userid, 5 is W or +, 6/7 are old/new SHAs
+ # 8 is reponame, 9 is refname (but all those are 1-based)
+ next unless $f[3] =~ /^(git-receive-pack|gl-reflog recover) /;
+ next unless $f[8];
+ next unless $f[7] eq $repo;
+ next unless $f[8] eq $ref;
+ push @loglines, $_;
+ }
+}
+
+if ( $cmd eq 'show' ) {
+ my $start = @loglines - $limit;
+ $start = 0 if $start < 0;
+ map { print "$loglines[$_]\n" } $start .. $#loglines;
+
+ exit 0;
+}
+
+if ( $cmd eq 'recover' ) {
+ my @f = split /\t/, $loglines[$#loglines];
+ die "the last push was not yours\n" unless $f[1] eq $ENV{GL_USER};
+ die "the last push was not a rewind or delete\n" unless $f[4] eq '+';
+
+ my($oldsha, $newsha) = @f[5,6];
+ if ($newsha =~ /^0+$/) {
+ print "recovering $repo $ref at $oldsha (was deleted)\n";
+ } else {
+ print "recovering $repo $ref at $oldsha (was forced to $newsha)\n";
+ }
+ chdir("$ENV{GL_REPO_BASE_ABS}/$repo.git");
+
+ my $newsha2 = $newsha;
+ $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");
+}
View
63 files/adc/help
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+. $(dirname $0)/adc.common-functions
+
+# the help adc now takes some options; we need to process them first
+
+[ "$1" = "-list" -o "$HELP_LIST_DEFAULT" = "1" ] && {
+ # the GL_ADC_PATH directory has files other than ADCs also, notably the
+ # include file for shell ADCs, and maybe a README or two. Those should be
+ # chmod -x.
+
+ # if you want to temporarily hide any ADC from being listed, do the same
+ # thing: chmod -x
+
+ cd $($GL_BINDIR/gl-query-rc GL_ADC_PATH)
+ for i in *
+ do
+ [ -x $i ] && echo $i
+ done
+
+ exit 0
+}
+
+# the local site can have a file called gl-adc-help.txt, which will be used as
+# the *entire* help text for this site...
+
+[ -f $HOME/gl-adc-help.txt ] && {
+ cat $HOME/gl-adc-help.txt
+ exit 0
+}
+
+# or the local site will use the default help text in this file, with an
+# optional pre- and post- text that is site local (like maybe the admin's
+# contact details)
+
+# pre
+[ -f $HOME/gl-adc-pre-help.txt ] && cat $HOME/gl-adc-pre-help.txt
+
+# default help text
+echo "
+
+The following adc's (admin-defined commands) are available at this site.
+
+creating a 'fork' of a repo:
+ the 'fork' adc forks a repo that you have read access to, to a repo that
+ you have create rights to
+
+deleting/trashing repos:
+ You can permanently remove a repo using 'rm'. By default, repos are
+ protected ('lock'ed) from being 'rm'-ed. You have to first 'unlock' a
+ repo before you can 'rm' it.
+
+ A different scheme of handling this is to use 'trash' to move the repo to
+ a 'trashcan' area. You can then 'list-trash' to see what you have, and
+ you can then 'restore' whichever repo you need to bring back.
+
+ More details can be found in contrib/adc/repo-deletion.mkd (or online at
+ http://sitaramc.github.com/gitolite/wild_repodel.html)
+
+"
+
+# post
+[ -f $HOME/gl-adc-post-help.txt ] && cat $HOME/gl-adc-post-help.txt
View
30 files/adc/htpasswd
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+BEGIN {
+ 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};
+}
+use gitolite_rc;
+use gitolite;
+
+die "$HTPASSWD_FILE doesn't exist or is not writable\n" unless -w $HTPASSWD_FILE;
+$|++;
+print <<EOFhtp;
+Please type in your new htpasswd at the prompt. You only have to type it once.
+
+NOTE THAT THE PASSWORD WILL BE ECHOED, so please make sure no one is
+shoulder-surfing, and make sure you clear your screen as well as scrollback
+history after you're done (or close your terminal instance).
+
+EOFhtp
+print "new htpasswd:";
+
+my $password = <>;
+$password =~ s/[\n\r]*$//;
+die "empty passwords are not allowed\n" unless $password;
+my $rc = system("htpasswd", "-mb", $HTPASSWD_FILE, $ENV{GL_USER}, $password);
+die "htpasswd command seems to have failed with $rc return code...\n" if $rc;
View
470 files/adc/hub
@@ -0,0 +1,470 @@
+#!/usr/bin/perl -w
+
+# SECURITY: look for the word SECURITY below and decide...
+
+# handle pull-requests and related stuff
+
+# developer notes:
+# - 'requestor' is too long, so I use "bob"; if you see the documentation
+# you'll realise this isn't as crazy as you think :-)
+
+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};
+
+sub usage {
+ print STDERR <<'EOF';
+GENERAL USAGE: ssh git@server hub <hub-command> <args>
+
+See docs for concepts; this usage message is only a refresher!
+
+Requestor's commands (repo child):
+ request-pull child b1 [parent]
+ request-status child [parent] [request-number]
+Parent repo owner's commands (repo parent):
+ list-requests parent
+ view-request parent request-number
+ view-log parent request-number <git log options>
+ view-diff parent request-number <git diff options>
+ reject parent request-number
+ fetch parent request-number
+ accept parent request-number
+
+EOF
+ exit 1;
+}
+
+our $tempdir;
+END {
+ wrap_chdir($ENV{GL_REPO_BASE_ABS});
+ system("rm", "-rf", "$tempdir.git") if $tempdir and $tempdir =~ /gl-internal-temp-repo/;
+}
+
+my %dispatch = (
+ rp => \&rp,
+ 'request-pull' => \&rp,
+ rs => \&rs,
+ 'request-status' => \&rs,
+ lr => \&lr,
+ 'list-requests' => \&lr,
+ vr => \&vr,
+ 'view-request' => \&vr,
+ vl => \&vl,
+ 'view-log' => \&vl,
+ vd => \&vd,
+ 'view-diff' => \&vd,
+ reject => \&reject,
+ fetch => \&fetch,
+ accept => \&accept,
+);
+
+my $cmd = shift || '';
+usage() unless ($dispatch{$cmd});
+
+unshift @INC, $ENV{GL_BINDIR};
+require gitolite or die "parse gitolite.pm failed\n";
+gitolite->import;
+
+# find what is effectively GL_ADC_PATH, then get the config var we're interested in
+use FindBin;
+my $BASE_FETCH_URL = `. $FindBin::Bin/adc.common-functions; echo \$BASE_FETCH_URL`;
+chomp($BASE_FETCH_URL);
+my $GL_FORKED_FROM = `. $FindBin::Bin/adc.common-functions; echo \$GL_FORKED_FROM`;
+chomp($GL_FORKED_FROM);
+
+my @args = @ARGV; @ARGV = ();
+$dispatch{$cmd}->(@args);
+
+# -------------------- bob's commands
+
+sub rp {
+ # request-pull child b1 [parent]
+ usage() unless @_ == 2 or @_ == 3;
+
+ # implicitly gives owner-parent read access to part of child, so requestor
+ # should already have read access to child (to prevent someone gaining
+ # access to child by faking a pull request against it!)
+ # XXX would it be better to ensure it is writable by Bob, because how/why
+ # would he make a pull request if he didn't just write to it?
+ my ($repo, $creator) = readable_repo(shift);
+ my $ref = valid_ref($repo, shift);
+
+ # the parent is either explicitly given, or the name of the parent
+ # recorded by the 'fork' ADC is used
+ my $repo_to = shift || parent_repo($repo);
+ # requestor need not have any access to parent; it is quite possible he
+ # gets this via git-daemon or something, so we just need to make sure it's
+ # a valid repo
+ $repo_to = valid_repo($repo_to);
+
+ # the 'cover letter' message comes from STDIN
+ my $cover = join("", <>);
+
+ # now create/update the pull request file
+ cd2repo($repo_to);
+ my %hub = get_hub();
+ $hub{$repo}{$ref}{BOB} = $ENV{GL_USER};
+ $hub{$repo}{$ref}{COVER} = $cover;
+ $hub{$repo}{$ref}{TIME} = time();
+ $hub{$repo}{$ref}{STATUS} = 'pending';
+ dump_hub(%hub);
+}
+
+sub rs {
+ # request-status child [parent] [request-number]
+ usage() unless @_ > 0 and @_ < 4; # 1 or 2 or 3
+ # same checks as in 'rp' above
+ my ($repo_from, $creator) = readable_repo(shift);
+
+ my $repo;
+ if ($_[0] and $_[0] !~ /^\d+$/) {
+ # next arg is not a number, so it should be 'parent'
+ $repo = shift;
+ } else {
+ $repo = parent_repo($repo_from);
+ }
+ $repo = valid_repo($repo);
+
+ my $rqno = 0;
+ $rqno = shift if ($_[0] and $_[0] =~ /^\d+$/);
+
+ # there should not be any arguments left over
+ usage() if @_;
+
+ unless ($rqno) {
+ cd2repo($repo);
+ my %hub_full = get_hub();
+ return unless $hub_full{$repo_from};
+ my %hub; $hub{$repo_from} = $hub_full{$repo_from};
+
+ list_hub('', %hub);
+
+ return;
+ }
+
+ my ($child, $ref, %hub) = get_request_N($repo, $rqno);
+ # this also does a chdir to $repo, by the way
+
+ my %hub1; $hub1{$child}{$ref} = $hub{$child}{$ref};
+ list_hub('', %hub1);
+ print "\nMessage:\n$hub1{$child}{$ref}{COVER}\n";
+}
+
+# -------------------- alice's commands
+
+sub lr {
+ # list-requests parent [optional search strings]
+ usage() unless @_ >= 1;
+
+ # Alice must have write access to parent, otherwise she can't really
+ # accept a pull request eventually right?
+ my ($repo, $creator) = writable_repo(shift);
+ cd2repo($repo);
+ my %hub = get_hub();
+ return unless %hub;
+
+ # create the search pattern. ADC arg checking is very strict; it doesn't
+ # allow &, | etc., so we just generate an OR condition out of the pieces
+ my $patt = join("|", @_);
+ list_hub($patt, %hub);
+}
+
+sub vr {
+ # view-request parent request-number
+ usage() unless @_ == 2;
+ my ($repo, $n) = @_;
+ my ($child, $ref, %hub) = get_request_N($repo, $n);
+ # this also does a chdir to $repo, by the way
+
+ my %hub1; $hub1{$child}{$ref} = $hub{$child}{$ref};
+ list_hub('', %hub1);
+ print "\nMessage:\n$hub1{$child}{$ref}{COVER}\n";
+}
+
+sub vl {
+ # view-log parent request-number <git log options>
+ usage() unless @_ >= 2;
+
+ my ($repo, $n) = (shift, shift);
+ my ($child, $ref, %hub) = get_request_N($repo, $n);
+
+ # so now we can find the set of SHAs that we already have
+ # XXX should we include tags also?
+ my @known_shas = grep { chomp; } `git for-each-ref refs/heads --format='%(objectname)'`;
+
+ # make a copy of the child repo (Bob's repo) containing only the ref being
+ # offered for fetch, then cd to it. This is easier to do than to sanitise
+ # all possible git-log arguments. We're doing this to prevent Alice from
+ # seeing anything more than the ref offered.
+ temp_clone($child, $ref);
+
+ # verify the list of "known_shas" because what's known in Alice's repo may
+ # not be known here. While you're about it, negate them. (We don't want
+ # to use "--not" because we're not sure what arguments the user will want
+ # to add and we don't want to negate some of them by mistake
+ @known_shas = grep { $_ = `git rev-parse --verify -q $_`; chomp && s/^/^/ } @known_shas;
+
+ # run the log command
+ # XXX SECURITY XXX do we need to check these arguments? Don't forget they
+ # are restricted by $ADC_CMD_ARGS_PATT (defined in gitolite_rc.pm), which
+ # is pretty tight to start with, so we know this cannot be used to run
+ # external programs. The question is, are any of git-log's arguments
+ # dangerous in their own right?
+ my @args = ('git', 'log', $ref);
+ push @args, @known_shas if @known_shas;
+ check_SHAs($ref, @_);
+ # each SHA in @_ must be a parent of $ref. Non-shas are not allowed
+ # since all refs other than $ref have been deleted in the temp clone
+ push @args, @_ if @_;
+ system @args;
+}
+
+sub vd {
+ # view-diff parent request-number <git diff options>
+ usage() unless @_ >= 4;
+ # we just check for 4 arguments; I guess later on we could also check
+ # to make sure at least 2 of them are SHAs or something but unless
+ # there's a security risk it's not needed
+
+ my ($repo, $n) = (shift, shift);
+ my ($child, $ref, %hub) = get_request_N($repo, $n);
+ # this also does a chdir to $repo, by the way
+
+ # now go to the child repo (Bob's repo)
+ temp_clone($child, $ref);
+
+ # run the diff command
+ # XXX SECURITY XXX do we need to check these arguments? Don't forget they
+ # are restricted by $ADC_CMD_ARGS_PATT (defined in gitolite_rc.pm), which
+ # is pretty tight to start with, so we know this cannot be used to run
+ # external programs. The question is, are any of git-diff's arguments
+ # dangerous in their own right?
+ my @args = ('git', 'diff');
+ check_SHAs($ref, @_);
+ push @args, @_ if @_;
+ system @args;
+}
+
+sub reject {
+ # reject parent request-number
+ usage() unless @_ == 2;
+ my ($repo, $n) = @_;
+ writable_repo($repo); # yeah we're throwing away the return values
+ my ($child, $ref, %hub) = get_request_N($repo, $n);
+
+ map { die "request status is already '$_'\n" if $_ ne 'pending' } $hub{$child}{$ref}{STATUS};
+
+ # the 'cover letter' message comes from STDIN
+ my $cover = join("", <>);
+ $hub{$child}{$ref}{STATUS} = "rejected by $ENV{GL_USER}";
+ $hub{$child}{$ref}{COVER} .= "\n\nRejected. Message to requestor:\n$cover";
+ dump_hub(%hub);
+}
+
+sub fetch {
+ # fetch parent request-number
+ usage() unless @_ == 2;
+ my ($repo, $n) = @_;
+ writable_repo($repo); # yeah we're throwing away the return values
+ my ($child, $ref, %hub) = get_request_N($repo, $n);
+
+ map { die "request status is already '$_'\n" if $_ ne 'pending' } $hub{$child}{$ref}{STATUS};
+
+ print "user $hub{$child}{$ref}{BOB} asked you to\n\tgit fetch $BASE_FETCH_URL/$child $ref\n";
+ print "hit enter to accept the fetch request or Ctrl-C to cancel...";
+ <>;
+
+ my $fetched_ref = "refs/heads/requests/child/$ref";
+ # you're already chdir'd to parent, by get_request_N
+ system("git", "update-ref", "-d", "refs/heads/$fetched_ref");
+ system("git", "fetch", "$ENV{GL_REPO_BASE_ABS}/$child.git", "$ref:$fetched_ref");
+
+ $hub{$child}{$ref}{STATUS} = "fetched by $ENV{GL_USER}";
+ dump_hub(%hub);
+}
+
+sub accept {
+ # accept parent request-number
+ usage() unless @_ == 2;
+ my ($repo, $n) = @_;
+ writable_repo($repo); # yeah we're throwing away the return values
+ my ($child, $ref, %hub) = get_request_N($repo, $n);
+
+ map { die "request status is '$_'; must be 'fetched'\n" if $_ !~ /^fetched by / } $hub{$child}{$ref}{STATUS};
+
+ # the 'cover letter' message comes from STDIN
+ my $cover = join("", <>);
+ $hub{$child}{$ref}{STATUS} = "accepted by $ENV{GL_USER}";
+ $hub{$child}{$ref}{COVER} .= "\n\nAccepted. Message to requestor:\n$cover";
+ dump_hub(%hub);
+}
+
+# -------------------- service subs
+
+sub assert {
+ my ($expr, $message) = @_;
+ eval $expr or die ($message ? "$message\n" : "assert '$expr' failed\n");
+}
+
+sub cd2repo {
+ my $repo = shift;
+ wrap_chdir("$ENV{GL_REPO_BASE_ABS}/$repo.git");
+}
+
+sub dump_hub {
+ # pwd assumed to git repo.git; dump a file called "gl-adc-hub-requests"
+ use Data::Dumper;
+ $Data::Dumper::Indent = 1;
+ $Data::Dumper::Sortkeys = 1;
+ my %hub = @_;
+
+ my $fh = wrap_open(">", "gl-adc-hub-requests");
+ print $fh Data::Dumper->Dump([\%hub], [qw(*hub)]);
+ close $fh;
+}
+
+sub get_hub {
+ # pwd assumed to git repo.git; "do" a file called "gl-adc-hub-requests"
+
+ return () unless -w "gl-adc-hub-requests";
+ our %hub = ();
+ do "gl-adc-hub-requests" or die "error parsing gl-adc-hub-requests\n";
+ return %hub;
+}
+
+sub get_request_N {
+ # given a repo and an N, return "child", "ref", and %hub (or die trying!)
+
+ # you can't look at pull requests for repos you don't have at least read access to
+ my ($repo, $creator) = readable_repo(shift);
+ cd2repo($repo);
+ my %hub = get_hub();
+ die "you have no pending requests\n" unless %hub;
+
+ my $n = shift || '';
+ usage() unless ($n =~ /^\d+$/);
+
+ my @hub = hub_sort(%hub);
+ die "you have only " . scalar(@hub) . " requests\n" if @hub < $n;
+ $n--; # make it 0-relative
+ return ($hub[$n]->{REPO}, $hub[$n]->{REF}, %hub);
+}
+
+sub hub_sort {
+ my %hub = @_;
+ my %sorted_hub = ();
+ for my $child (sort keys %hub) {
+ for my $ref (sort keys %{ $hub{$child} }) {
+ my $key = $hub{$child}{$ref}{TIME} . "-$child-$ref";
+ $sorted_hub{$key} = { REPO=>$child, REF=>$ref };
+ }
+ }
+ my @hub = ();
+ for my $key (sort keys %sorted_hub) {
+ push @hub, $sorted_hub{$key};
+ }
+ return @hub;
+}
+
+sub list_hub {
+ my ($status, %hub) = @_;
+
+ my $header = "#\tchild-repository-name\t(requestor)\tbranch-or-tag-to-pull\tstatus\n----\n";
+ my @hub = hub_sort(%hub);
+ my $sn = 0;
+ for my $pr (@hub) {
+ $sn++;
+ my $child = $pr->{REPO};
+ my $ref = $pr->{REF};
+ my $pr_status = $hub{$child}{$ref}{STATUS};
+ next if $status and $pr_status !~ /$status/;
+ print $header if $header; $header = '';
+ print "$sn\t$child\t($hub{$child}{$ref}{BOB})\t$ref\t$pr_status\n";
+ }
+}
+
+sub parent_repo {
+ my ($repo) = shift;
+ cd2repo($repo);
+ die "parent repo was not recorded, sorry!\n" unless -f $GL_FORKED_FROM;
+ my $gff = `cat $GL_FORKED_FROM`;
+ chomp($gff);
+ return $gff;
+}
+
+sub readable_repo {
+ my $repo = valid_repo(shift);
+ my ($perm, $creator) = check_access($repo);
+ die "$repo does not exist or you have no read access\n" unless $perm =~ /R/;
+ return ($repo, $creator);
+}
+
+sub valid_log_options {
+}
+
+sub valid_ref {
+ my ($repo, $ref) = @_;
+ cd2repo($repo);
+ die "invalid ref $ref\n" unless `git cat-file -t $ref` =~ /^commit$/;
+ die "invalid ref $ref\n" unless `git rev-parse $ref` =~ /^[0-9a-f]{40}$/;
+ return $ref;
+}
+
+sub valid_repo {
+ my $repo = shift;
+ $repo =~ s/\.git$//;
+ die "$repo does not exist or you have no read access\n" unless -d "$ENV{GL_REPO_BASE_ABS}/$repo.git";
+ return $repo;
+}
+
+sub writable_repo {
+ my $repo = valid_repo(shift);
+ my ($perm, $creator) = check_access($repo);
+ die "$repo does not exist or you have no write access\n" unless $perm =~ /W/;
+ return ($repo, $creator);
+}
+
+sub temp_clone {
+ my ($repo, $ref) = @_;
+
+ die "internal error; temp_clone called twice?\n" if $tempdir;
+
+ # some of this code is also in "rrr" branch
+
+ # first make a temp directory within $REPO_BASE
+ $ENV{TMPDIR} = $ENV{GL_REPO_BASE_ABS};
+ $tempdir = `mktemp -d -t gl-internal-temp-repo.XXXXXXXXXX`;
+ chomp($tempdir);
+ rename $tempdir, "$tempdir.git";
+
+ # make the clone
+ wrap_chdir("$ENV{GL_REPO_BASE_ABS}");
+ system("git clone --mirror -l $repo.git $tempdir.git >/dev/null 2>&1");
+
+ # go to the clone and delete refs he's not allowed to read
+ wrap_chdir("$ENV{GL_REPO_BASE_ABS}");
+ wrap_chdir("$tempdir.git");
+ # for each available ref
+ for my $ar (`git for-each-ref refs '--format=%(refname)'`) {
+ chomp($ar);
+ system('git', 'update-ref', '-d', $ar) unless $ar eq "refs/heads/$ref";
+ }
+
+ # you've already cd-d to the temp repo, just set the name up properly
+ $tempdir =~ s/^\Q$ENV{GL_REPO_BASE_ABS}\///;
+}
+
+sub check_SHAs {
+ my $ref = shift;
+ for (@_) {
+ next unless /^[0-9a-f]+$/i;
+ my $fullsha = `git rev-parse $_`;
+ chomp($fullsha);
+ die "invalid SHA: $_\n" unless $fullsha =~ /^[0-9a-f]{40}$/;
+ my $mergebase = `git merge-base $fullsha $ref`;
+ chomp($mergebase);
+ die "invalid SHA: $_\n" unless $mergebase eq $fullsha;
+ }
+}
View
184 files/adc/hub.mkd
@@ -0,0 +1,184 @@
+# F=hub the 'hub' ADC
+
+## a home grown 'hub' for git repos
+
+This ADC (admin-defined command) helps collaboration among repos. The name is
+in honor of github, which is the primary host for gitolite itself.
+
+[Note that github is a web-based service, and does a lot more, like comments,
+code reviews, etc., none of which are possible here. We're only talking about
+some basic stuff to make the most common operations easier. In particular,
+this system is not a replacement for normal project communications like
+email!]
+
+**Conventions used**: all through the following description, we will assume
+that **Alice** has a repo **parent**, **Bob** has a fork of parent called
+**child**, and he is asking Alice to pull a branch he made called **b1** from
+child to parent.
+
+In plain git (without using github or similar), the pull request process
+starts with an email from Bob to Alice, followed by Alice running a `git fetch
+<Bob's URL> b1` (optionally preceded by a `git remote add` for convenience of
+long term use). Until this happens she can't see much detail about the
+commits to be pulled.
+
+**What this ADC does** is (a) collect all the pull requests she needs to look
+at in one place, and (b) allow her to examine the changes with any combination
+of `git log` and `git diff` options *without having to fetch the content
+first*, and (c) act as a trusted intermediary to allow Alice to fetch *just
+one branch* from Bob even if she does not have any access to Bob's repository!
+
+In a situation where there may be lots of requests, being able to take a quick
+look at them (and possibly reject some), without having to pull down anything
+at all, could be very useful.
+
+Once past that level, she could get the changes down to her workstation using
+`git fetch` as normal. With this ADC, however, she has another alternative:
+get them over to `parent` (her repo) on the same gitolite server, then later
+do a normal `git fetch [origin]` to get it to her workstation. This has the
+added advantage that other people, who may be watching her repo but not Bob's,
+now get to see what Bob sent her and send comments etc.
+
+## general syntax
+
+The general syntax is
+
+ ssh git@server hub <hub-command> <args>
+
+### Bob's commands
+
+The following commands do not cause a fetch, and should be quite fast:
+
+ * Bob sends a pull request for branch b1 to repo parent. Notice he does not
+ mention Alice by name. This command expects a message to be piped/typed
+ in via STDIN [this message is meant to be transient and is not stored long
+ term; use email for more "permanent" communications].
+
+ echo "hi Alice, please pull" | ssh git@server hub request-pull child b1 [parent]
+
+ If `child` was created by a recent version of the 'fork' ADC (or the KDE
+ 'clone' ADC), which records the name of the parent repo on a fork, and it
+ is *that* repo to which Bob wishes to send the pull request, the third
+ argument is optional.
+
+ * Bob lists the status (fetched/rejected/pending) of pull requests he has
+ made from his repo child to repo parent. (Note we don't say "accepted" but
+ "fetched"; see later for why):
+
+ ssh git@server hub request-status child [parent]
+
+ The second argument is optional the same way as the 3rd argument in the
+ previous command.
+
+ Requests that have been accepted or rejected will usually have some
+ additional text, supplied by the user who did the reject/accept. Bob can
+ ask for those details by request number:
+
+ ssh git@server hub request-status child [parent] request-number
+
+### Alice's "just looking" commands
+
+ * Alice lists requests waiting for her to check and possibly pull into
+ parent. For each waiting pull request, she will see a serial number, the
+ originating repo name (child, in our example), the requestor (Bob, here),
+ and the branch/tag-name (b1) being pulled:
+
+ ssh git@server hub list-requests parent
+
+ This command also takes an optional list of search strings that are OR-d
+ together and matched against the 'status' field. So saying
+
+ ssh git@server hub list-requests parent fetched pending
+
+ would list only items that were 'fetched' or 'pending' (meaning 'accepted'
+ and 'rejected' would not show up).
+
+ * Alice views request # 1 waiting to be pulled into parent. Shows the same
+ details as above for that request, followed by the message that Bob typed
+ in when he ran `request-pull`:
+
+ ssh git@server hub view-request parent 1
+
+ * Alice views the log of the branch she is being asked to pull. Note that
+ this does NOT involve a fetch, so it will be pretty fast. The log starts
+ from b1, and stops at a SHA that represents any of the branches in parent.
+ Alice can use any git-log options she wants to; for instance `--graph`,
+ `--decorate`, `--boundary`, etc., could be quite useful. However, she
+ can't use any GUI; it has to be 'git log':
+
+ ssh git@server hub view-log parent 1 <git log options>
+
+ Notice that the repo name Alice supplies is still her own, although the
+ log comes from the child repo that Bob wants Alice to pull from. It's
+ best if you think of this as "view the commits from pull request #1 in
+ 'parent'".
+
+ * Alice views the diff between arbitrary commits on child:
+
+ ssh git@server hub view-diff parent 1 <git diff options>
+
+ Again, she mentions *her* reponame but the diff's come from `child`. Also
+ note that, due to restrictions on what characters are allowed in arguments
+ to ADCs, you probably can't do things like `pu^` or `master~3`, and have
+ to use SHAs instead.
+
+### Alice's "action" commands
+
+ * Alice doesn't like what she sees and decides to reject it. This command
+ expects some text on STDIN as the rejection message:
+
+ echo "hi Bob, your patch needs work; see email" | ssh git@server hub reject parent 1
+
+ * Alice likes what she sees so far and wants to fetch the branch Bob is
+ asking her to pull. Note that we are intentionally not using the word
+ "accept", because this command does not (and cannot, since it is running
+ on a bare repo on the server) do a pull. What it does is it fetches into
+ a branch whose name will be `requests/child/b1`.
+
+ Note that when multiple requests from the same repo (child) for the same
+ branch (b1) happen, each "fetch" overwrites the branch. This allows Bob
+ to continually refine the branch he is requesting for a pull based on
+ (presumably emailed) comments from Alice. In a way, this is a "remote
+ tracking branch", just like `refs/remotes/origin/b1`.
+
+ ssh git@server hub fetch parent 1
+
+ This command will actually fetch from child into parent, and may take time
+ when the changes are large. However all this is on the server so it does
+ not involve network traffic:
+
+ * Alice has fetched the stuff she wants, looked at it/tested it, and decides
+ to merge it into `parent`. Once that is done, she runs:
+
+ echo "thanks for the frobnitz patch Bob" | ssh git@server hub accept parent 1
+
+ to let Bob know next time he checks 'request-status'. Like the `reject`
+ sub-command, this is also just a status update; no actual 'git' changes
+ happen.
+
+Notice the sequence of Alice's action commands: it's either 'reject', or a
+'fetch' then 'accept'.
+
+## what next?
+
+At this point, you're done with the `hub` ADC. However, all this is on the
+bare `parent.git` on the server, and nothing has hit Alice's workstation yet!
+Alice will still have to run a fetch or a pull on her workstation if she wants
+to check the code in detail, use a tool like gitk, and especially to
+compile/test it. *Then* she decides whether to accept or reject the request,
+which will have to be communicated via email, of course; see the second para
+of this document ;-)
+
+Finally, note that Alice does not actually need to use the `fetch` subcommand.
+She can do the traditional thing and fetch Bob's repo/branch directly to her
+*workstation*.
+
+## note to the admin: configuration variables
+
+There are 2 configuration variables. `BASE_FETCH_URL` should be set to a
+simple "read" URL (so it doesn't even have to be ssh) that almost anyone using
+this server can use. It's only used in producing the `git fetch` command
+mentioned just above.
+
+`GL_FORKED_FROM` is set to `gl-forked-from` by default, but if your initials
+are `JM` you can set it to `kde-cloned-from` to save time and trouble ;-)
View
13 files/adc/list-trash
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+. $(dirname $0)/adc.common-functions
+
+# this is a helper ADC for "trash"; see that one for option settings etc
+
+cd $TRASH_CAN 2>/dev/null || exit 0
+find . -name gl-creater | sort | while read t
+do
+ owner=
+ owner=`cat "$t"`
+ [ "$owner" = "$GL_USER" ] && dirname $t
+done | cut -c3-
View
12 files/adc/lock
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+. $(dirname $0)/adc.common-functions
+
+# this is a helper ADC for "rm"; see that one for documentation
+
+# cd to repo base and make sure arg1 is a valid repo (also sets $repo)
+valid_owned_repo $1
+
+rm -f $repo.git/gl-rm-ok
+
+echo "$repo has been locked. Please run the 'help' adc for more info."
View
115 files/adc/perms
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# Original author: Richard Bateman <taxilian@gmail.com>
+#
+# Any questions or concerns about how this works should be addressed to
+# me, not to sitaram. Please note that neither I nor sitaram make any
+# guarantees about the security or usefulness of this script. It may
+# be used without warantee or any guarantee of any kind.
+#
+# That said, it works fine for me.
+#
+# This script is licensed under the New BSD license
+# Copyright 2011 Richard Bateman
+#
+
+import sys, os
+from pygitolite import *
+
+def list(gl, user, repo, filter_var = ""):
+ perms = gl.get_perms(repo, user)
+ for var, ppl in perms.iteritems():
+ if filter_var == "" or filter_var == var:
+ print "%s:" % var
+ for item in ppl:
+ print " %s" % item
+
+def clear(gl, user, repo, filter_var = ""):
+ try:
+ os.system(r"echo Are you sure? Type YES \(all caps\) to continue: ")
+ bval = raw_input()
+ if bval != "YES":
+ print "Canceling..."
+
+ if filter_var == "":
+ gl.set_perms(repo, user, {})
+ else:
+ perms = gl.get_perms(repo, user)
+ if filter_var in perms:
+ del perms[filter_var]
+ gl.set_perms(repo, user, perms)
+ print "Perms after clear:"
+ list(gl, user, repo)
+ except:
+ print "An error occured"
+
+def add(gl, user, repo, var, *users):
+ perms = gl.get_perms(repo, user)
+ if var not in perms:
+ perms[var] = []
+ if len(users) == 0:
+ print "Usage: perms add %s %s <username>" % (repo, var)
+ return
+ for cur in users:
+ if cur not in perms[var]:
+ perms[var].append(cur)
+ gl.set_perms(repo, user, perms)
+ list(gl, user, repo, var)
+
+def set(gl, user, repo, var, *users):
+ perms = gl.get_perms(repo, user)
+ perms[var] = []
+ if len(users) == 0:
+ print "Usage: perms set %s %s <username>" % (repo, var)
+ return
+ for cur in users:
+ if cur not in perms[var]:
+ perms[var].append(cur)
+ gl.set_perms(repo, user, perms)
+ list(gl, user, repo, var)
+
+def remove(gl, user, repo, var, *users):
+ perms = gl.get_perms(repo, user)
+ if var not in perms:
+ print "%s isn't a valid type" % var
+ return
+ if len(users) == 0:
+ print "No users specified to remove; perhaps you want clear?"
+ return
+ for cur in users:
+ if cur in perms[var]:
+ perms[var].remove(cur)
+ gl.set_perms(repo, user, perms)
+ list(gl, user, repo, var)
+
+commands = {
+ "list": list,
+ "clear": clear,
+ "add": add,
+ "set": set,
+ "remove": remove,
+ }
+
+if __name__ == "__main__":
+ if "GL_USER" not in os.environ:
+ raise "No user!"
+ user = os.environ["GL_USER"]
+ command = sys.argv[1] if len(sys.argv) > 2 else ""
+ if len(sys.argv) < 3 or command not in commands:
+ print "Usage: perms <command> <repository> <args>"
+ print " list <repository> [TYPE]"
+ print " clear <repository>"
+ print " add <repository> <TYPE> [user and group list]"
+ print " set <repository> <TYPE> [user and group list]"
+ print " remove <repository> <TYPE> [user and group list]"
+ sys.exit(1)
+ repo = sys.argv[2]
+
+ gl = gitolite()
+ rights, owner = gl.get_rights_and_owner(repo, user)
+
+ if owner != user:
+ print "Either %s does not exist or you are not the owner." % repo
+ sys.exit(1)
+
+ commands[command](gl, user, repo, *sys.argv[3:])
View
77 files/adc/pygitolite.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+#
+# Original author: Richard Bateman <taxilian@gmail.com>
+#
+# Any questions or concerns about how this works should be addressed to
+# me, not to sitaram. Please note that neither I nor sitaram make any
+# guarantees about the security or usefulness of this script. It may
+# be used without warantee or any guarantee of any kind.
+#
+# This script is licensed under the New BSD license
+# Copyright 2011 Richard Bateman
+#
+
+import sys, os, subprocess
+
+class gitolite(object):
+ def __init__(self, **kvargs):
+ self.GL_BINDIR = kvargs["GL_BINDIR"] if "GL_BINDIR" in kvargs else os.environ["GL_BINDIR"]
+ self.user = kvargs["GL_USER"] if "GL_USER" in kvargs else os.environ["GL_USER"]
+ pass
+
+ def gitolite_execute(self, command, std_inputdata = None):
+ cmd = "perl -I%s -Mgitolite -e '%s'" % (self.GL_BINDIR,command)
+ p = subprocess.Popen(cmd, shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE, stdin = subprocess.PIPE)
+ stdout, stderr = p.communicate(std_inputdata)
+ if p.returncode is not 0:
+ raise Exception(stderr)
+ return stdout.strip()
+
+ def run_custom_command(self, repo, user, command, extra = None):
+ os.environ["SSH_ORIGINAL_COMMAND"] = "%s %s" % (command, repo)
+ return self.gitolite_execute('run_custom_command("%s")' % user, extra)
+
+ def get_perms(self, repo, user):
+ full = self.run_custom_command(repo, user, "getperms")
+ plist = full.split("\n")
+ perms = {}
+ for line in plist:
+ if line == "":
+ continue
+ var, strlist = line.split(" ", 1)
+ perms[var] = strlist.split(" ")
+
+ return perms
+
+ def set_perms(self, repo, user, perms):
+ permstr = ""
+ for var, curlist in perms.iteritems():
+ if len(curlist) == 0:
+ continue;
+ varstr = var
+ for cur in curlist:
+ varstr += " %s" % cur
+ permstr = permstr + "\n" + varstr
+ resp = self.run_custom_command(repo, user, "setperms", permstr.strip())
+
+ def valid_owned_repo(self, repo, user):
+ rights, user = self.get_rights_and_owner(repo, user)
+ return owner == user
+
+ def get_rights_and_owner(self, repo, user):
+ if not repo.endswith(".git"):
+ repo = "%s.git" % repo
+ ans = self.gitolite_execute('cli_repo_rights("%s")' % repo)
+ perms, owner = ans.split(" ")
+ rights = {"Read": "R" in perms, "Write": "W" in perms, "Create": "C" in perms}
+ return rights, owner
+
+if __name__ == "__main__":
+ if "GL_USER" not in os.environ:
+ raise "No user!"
+ user = os.environ["GL_USER"]
+ repo = sys.argv[1]
+
+ gl = gitolite()
+ print gl.get_rights_and_owner(repo, user)
+ print gl.get_perms(repo, user)
View
55 files/adc/repo-deletion.mkd
@@ -0,0 +1,55 @@
+# F=wild_repodel deleting repos safely
+
+**NOTE**: this page is about deleting [user-created repos][wild]. It is
+**not** about deleting "normal" repos (the kind that are specified in the
+gitolite.conf file itself) -- to delete those read [here][repodel].
+
+(see [this thread][thr] on the gitolite mailing list)
+
+[thr]: http://groups.google.com/group/gitolite/browse_thread/thread/fb9cf5a464b6dfee
+
+By default, the old 'rmrepo' ADC (admin-defined command) just went and deleted
+the repo -- no questions asked! Sometimes, that could be a disaster -- you
+lose the whole thing in one mad moment of typo-ing or frustration. Ouch.
+
+This has been replaced by 2 families of ADCs. I say "families" because each
+has one main command and 2 ancillary ones. Admins can choose to install
+either, both, or neither family of commands.
+
+Local settings for these ADCs can be found in the common settings file
+"adc.common-functions".
+
+1. 'rm' will remove the repo. If `USE_LOCK_UNLOCK` is set, rm will refuse to
+ remove a locked repo. All repos are locked by default, and you have to
+ explicitly 'unlock' a repo to remove it. You can also 'lock' it again
+ instead of removing it of course.
+
+ There's also `ARE_YOU_SURE`, for situations where a simple warning
+ suffices.
+
+ You can also use both these flags if you wish.
+
+2. 'trash' will move the repo to a safe location. There are settings for
+ where this location is and what suffix is added to the repo name. You can
+ 'list-trash' to see what trash you have collected, and you can 'restore'
+ one of the listed repos.
+
+ It's easy to automatically clean out the trash occasionally. By default,
+ entries in the trash look like this:
+
+ foo/r1/2010-10-22_13:14:24
+ foo/r1/2010-10-22_13:14:50
+
+ This shows a repo foo/r1 that was created and trashed twice.
+
+ Since the date appears in the name, you can use it with a cutoff to clean
+ up old repos. Untested example:
+
+ cutoff=`date -I -d '28 days ago'`
+ find $TRASH_CAN -type d -name "20??-??-??_*" | while read r
+ do
+ d=`basename $r`
+ [[ $d < $cutoff ]] && rm -rf $r
+ done
+
+ Put this in cron to run once a day and that should be it.
View
16 files/adc/restore
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+. $(dirname $0)/adc.common-functions
+
+repo=$1
+[ -z "$1" ] && die need a repo name
+
+owner=
+owner=`cat $TRASH_CAN/$repo/gl-creater 2>/dev/null`
+[ "$owner" = "$GL_USER" ] || die "$repo is not yours!"
+
+cd $TRASH_CAN
+realrepo=`dirname $repo`
+[ -d $GL_REPO_BASE_ABS/$realrepo.git ] && die $realrepo already exists
+mv $repo $GL_REPO_BASE_ABS/$realrepo.git
+echo $repo restored to $realrepo
View
10 files/adc/restrict-admin
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+. $(dirname $0)/adc.common-functions
+
+is_admin || die "just *what* are you trying to pull, young man?"
+
+# and here you let them do the dangerous stuff
+echo "+rm -rf $GL_REPO_BASE_ABS"
+sleep 2
+echo ...just kidding!
View
40 files/adc/rm
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+. $(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
+# As shipped, both options are set. If you set both of them to "0", repos are
+# just deleted blindly, with no confirmation
+
+# helper ADCs: lock, unlock
+
+# cd to repo base and make sure arg1 is a valid repo (also sets $repo)
+valid_owned_repo $1
+
+opt $USE_LOCK_UNLOCK && {
+ if [ -f $repo.git/gl-rm-ok ]
+ then
+ :
+ else
+ die "$repo is locked! Please run the 'help' adc for more info."
+ fi
+}
+
+opt $ARE_YOU_SURE && {
+ echo "Are you sure? (type 'yes' if you are)" >&2
+ read s
+ [ $s = "yes" ] || die aborting...
+}
+
+rm -rf $repo.git
+echo "$repo is now GONE!"
+
+# gitweb specific; code for cgit users left as an exercise for the reader
+cd $HOME
+PROJECTS_LIST=$($GL_BINDIR/gl-query-rc PROJECTS_LIST)
+export repo
+perl -ni -e 'print unless /^\Q$ENV{repo}.git\E$/' $PROJECTS_LIST
View
4 files/adc/rmrepo
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+echo "this command no longer exists. Please run the 'help' adc for more info."
+exit 1
View
76 files/adc/rsync
@@ -0,0 +1,76 @@
+#!/usr/bin/perl
+
+# 'rsync' helper ADC. See bottom of this file for more info
+
+use strict;
+use warnings;
+
+BEGIN {
+ 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};
+}
+use gitolite_rc;
+use gitolite;
+
+my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
+
+# test the command patterns; reject if they don't fit. Rsync sends
+# commands that looks like one of these to the server (the first one is
+# for a read, the second for a write)
+# rsync --server --sender -some.flags . some/path
+# rsync --server -some.flags . some/path
+
+die "bad rsync command: $cmd"
+ unless $cmd =~ /^rsync --server( --sender)? -[\w.]+(?: --(?:delete|partial))* \. (\S+)$/;
+my $perm = "W";
+$perm = "R" if $1;
+my $path = $2;
+die "I dont like some of the characters in $path\n" unless $path =~ $REPONAME_PATT;
+ # please see notes below on replacing this line if needed
+die "I dont like absolute paths in $cmd\n" if $path =~ /^\//;
+die "I dont like '..' paths in $cmd\n" if $path =~ /\.\./;
+
+# ok now check if we're permitted to execute a $perm action on $path
+# (taken as a refex) using rsync.
+
+my $ret = check_access('EXTCMD/rsync', "NAME/$path", $perm, 1);
+die "$perm NAME/$path $ENV{GL_USER} $ret\n" if $ret =~ /DENIED/;
+
+wrap_chdir($RSYNC_BASE);
+log_it();
+exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND};
+
+__END__
+
+This is an rsync helper ADC. It is an example of using gitolite's config
+language, combined with the 'check_access()' function, to implement access
+control for non-git software using a "fake" repo. For historical reasons,
+fake repos start with "EXTCMD/". Gitolite does not auto-create fake repos, so
+you can use those as namespaces to hold collections of rules for various
+purposes.
+
+So here's a fake git repository to collect rsync rules in one place. It
+grants permissions to files/dirs within the $RSYNC_BASE tree. A leading NAME/
+is required as a prefix; the actual path starts after that. Matching follows
+the same rules as given in "FILE/DIR NAME BASED RESTRICTIONS" elsewhere in the
+gitolite documentation.
+
+ repo EXTCMD/rsync
+ RW NAME/ = sitaram
+ RW NAME/foo/ = user1
+ R NAME/bar/ = user2
+ RW NAME/baz/.*/.*\.c$ = user3
+
+Finally, if the filepaths your users are reading/writing have names that fall
+outside ADC_CMD_ARGS_PATT, see the "passing unchecked arguments" section in
+doc/admin-defined-commands.mkd (online at [1]).
+
+[1]: http://sitaramc.github.com/gitolite/doc/admin-defined-commands.html#_passing_unchecked_arguments
+
+If you do this, you will also need to replace the line above (where $path is
+being matched against $REPONAME_PATT) with an equivalent check of your own.
+Remember that whole command is being sent off to be executed by the *SHELL*.
+
+It may be best to split it into arguments and call rsync directly, preventing
+issues with shell metas. Patches welcome ;-)
View
150 files/adc/s3backup
@@ -0,0 +1,150 @@
+#!/usr/bin/perl
+# Copyright 2011, David Bremner <bremner@debian.org>
+#
+# This add-on to gitolite is licensed under the same terms as gitolite
+# At the time of this writing this is GPL-2, but I also grant
+# Sitaram Chamarty the right to re-license as he chooses.
+
+=pod
+
+S3BACKUP - Backup the whole gitolite home directory to amazon s3.
+
+RUNNING
+
+To run it (assuming you call this "s3backup")
+As an ADC, only by a user with read access to gitolite-admin :
+
+ ssh git@server s3backup [-v error|warning|notice|info|debug] [subcommands]
+
+You must pass the AWS_SECRET_ACCESS_KEY on stdin.
+
+SUBCOMMANDS
+
+You may optionally pass one of the following sub commands
+
+=over
+
+=item incr|full
+
+Run incremental or full backup. By default, leave it to duplicity to
+guess.
+
+=item prune
+
+remove all but $S3_KEEP_FULL (see Configuration below)
+
+=back
+
+OPTIONS
+
+-v specifies a log level, passed straight to duplicity.
+incr or full specify backup level
+
+CONFIGURATION
+
+Make a file ~git/.s3backup.rc that looks like
+
+ $S3_EUROPE=0; # 1 to store in europe
+ # (only matters at creation)
+ $S3_KEEP_FULL=3; # keep 3 full backups
+ $S3_BUCKET="my_bucket"; # s3 bucket name, will be created
+ # if it doesn't exist.
+ $S3_AWS_KEY_ID="ABADABA57DOO"; # this is the _non_ secret one
+ $S3_ENCRYPT_KEY="DEADBEEF AA232332"; # gpg keys, space delimited.
+ # note that duplicity will abort if
+ # these keys are not trusted
+=cut
+
+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 ($perm, $creator) = check_access("gitolite-admin");
+die "no read access to gitolite-admin\n" unless $perm =~ /R/;
+
+our ($S3_EUROPE, $S3_BUCKET, $S3_AWS_KEY_ID, $S3_ENCRYPT_KEYS, $S3_KEEP_FULL);
+
+chdir($ENV{HOME}) or die "chdir $ENV{HOME} : $!\n";
+
+my $configfile = $ENV{HOME} . "/.s3backup.rc";
+
+do $configfile or die "error parsing $configfile\n";
+
+# ANCHOR ALL PATTERNS
+die 'bad value for $S3_EUROPE'
+ unless (defined($S3_EUROPE) && $S3_EUROPE =~ m/^0|1$/);
+
+die 'bad value for $S3_KEEP_FULL'
+ unless (defined($S3_KEEP_FULL) && $S3_KEEP_FULL =~ m/^[0-9]+$/);
+
+die 'bad value for $S3_BACKUP'
+ unless (defined($S3_BUCKET) && $S3_BUCKET =~ m/^[a-z\-_0-9\.]+$/);
+
+die 'bad value for $S3_AWS_KEY_ID'
+ unless (defined($S3_AWS_KEY_ID) && $S3_AWS_KEY_ID =~ m/^[A-Z0-9]+$/);
+
+die 'bad value for $S3_ENCRYPT_KEYS'
+ unless (defined($S3_ENCRYPT_KEYS) &&
+ $S3_ENCRYPT_KEYS =~ m/^[a-fA-F0-9]+(\s+[a-fA-F0-9]+)*/);
+
+my $verbosity='notice';
+if (scalar(@ARGV)>0 and $ARGV[0] eq '-v'){
+ shift;
+ $verbosity = shift;
+}
+
+die "bad verbosity" unless ($verbosity =~ m/^(error|warning|notice|info|debug)$/);
+
+my $subcommand=shift || 'default';
+
+die "bad subcommand" if (defined($subcommand) &&
+ $subcommand !~ m/^(incr|full|prune|default)$/);
+
+$ENV{AWS_ACCESS_KEY_ID}=$S3_AWS_KEY_ID;
+
+chomp($ENV{AWS_SECRET_ACCESS_KEY}=<>) or
+ die "must pass SECRET_ACCESS_KEY on stdin";
+
+my @args=();
+
+if ($subcommand ne 'default' ){
+ if ($subcommand eq 'prune') {
+ push(@args, 'remove-all-but-n-full', $S3_KEEP_FULL, '--force');
+ } else {
+ push(@args, $subcommand);
+ }
+}
+
+push(@args, '--verb', $verbosity);
+push(@args, '--s3-use-new-style');
+foreach my $key (split(' ',$S3_ENCRYPT_KEYS)){
+ push(@args, '--encrypt-key', $key);
+}
+
+push(@args, '--s3-european-buckets') if ($S3_EUROPE);
+
+push(@args, $ENV{HOME}) unless ($subcommand eq 'prune');
+
+push(@args, 's3+http://'.$S3_BUCKET);
+
+my $semaphore=$ENV{HOME}."/.gitolite.down";
+
+die "$semaphore already exists" if (-f $semaphore);
+
+eval {
+ open (SEMFD,'>',$semaphore) or die ("failed to open $semaphore");
+ my $now = gmtime();
+ print SEMFD "Repo unavailable due to $subcommand backup started at $now GMT\n";
+ close SEMFD;
+
+ system '/usr/bin/duplicity', @args;
+
+};
+
+unlink $semaphore;
View
25 files/adc/setdesc
@@ -0,0 +1,25 @@
+#!/usr/bin/perl
+
+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 $repo = shift;
+die "need a reponame\n" unless $repo;
+
+my $ret = check_access($repo, 'refs/heads/master', '+', 1);
+
+die "sorry you don't have rights to do this\n" if $ret =~ /DENIED/;
+
+wrap_chdir($ENV{GL_REPO_BASE_ABS});
+wrap_chdir("$repo.git");
+
+wrap_print("description", <>);
+print "New description is:\n";
+print slurp("description");
View
275 files/adc/sskm
@@ -0,0 +1,275 @@
+#!/usr/bin/perl
+
+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};
+
+# pull in modules we need
+unshift @INC, $ENV{GL_BINDIR};
+require gitolite_rc or die "parse gitolite_rc.pm failed\n";
+gitolite_rc->import;
+require gitolite or die "parse gitolite.pm failed\n";
+gitolite->import;
+
+# get to the keydir
+die "keydir not accessible\n" unless -d $gitolite_rc::GL_KEYDIR;
+chdir($gitolite_rc::GL_KEYDIR);
+
+# save arguments for later
+my $operation = shift || 'list';
+my $keyid = shift || '';
+# keyid must fit a very specific pattern
+$keyid and $keyid !~ /^@[-0-9a-z_]+$/i and die "invalid keyid $keyid\n";
+
+# get the actual userid and keytype
+my $gl_user = $ENV{GL_USER};
+my $keytype = '';
+$keytype = $1 if $gl_user =~ s/^zzz-marked-for-(...)-//;
+print STDERR "hello $gl_user, you are currently using " .
+ ($keytype ? "a key in the 'marked for $keytype' state\n"
+ : "a normal (\"active\") key\n" );
+
+# ----
+# first collect the keys
+
+my (@pubkeys, @marked_for_add, @marked_for_del);
+# get the list of pubkey files for this user, including pubkeys marked for
+# add/delete
+
+for my $pubkey (`find . -type f -name "*.pub" | sort`) {
+ chomp($pubkey);
+ $pubkey =~ s(^./)(); # artifact of the find command
+
+ my $user = $pubkey;
+ $user =~ s(.*/)(); # foo/bar/baz.pub -> baz.pub
+ $user =~ s/(\@[^.]+)?\.pub$//; # baz.pub, baz@home.pub -> baz
+
+ next unless $user eq $gl_user or $user =~ /^zzz-marked-for-...-$gl_user/;
+
+ if ($user =~ m(^zzz-marked-for-add-)) {
+ push @marked_for_add, $pubkey;
+ } elsif ($user =~ m(^zzz-marked-for-del-)) {
+ push @marked_for_del, $pubkey;
+ } else {
+ push @pubkeys, $pubkey;
+ }
+}
+
+# ----
+# list mode; just do it and exit
+sub print_keylist {
+ my ($message, @list) = @_;
+ return unless @list;
+ print "== $message ==\n";
+ my $count=1;
+ for (@list) {
+ my $fp = fingerprint($_);
+ s/zzz-marked(\/|-for-...-)//g;
+ print $count++ . ": $fp : $_\n";
+ }
+}
+if ($operation eq 'list') {
+ print "you have the following keys:\n";
+ print_keylist("active keys", @pubkeys);
+ print_keylist("keys marked for addition/replacement", @marked_for_add);
+ print_keylist("keys marked for deletion", @marked_for_del);
+ print "\n\n";
+ exit;
+}
+
+# ----
+# please see docs for details on how a user interacts with this
+
+if ($keytype eq '') {
+ # user logging in with a normal key
+ die "valid operations: add, del, undo-add, confirm-del\n" unless $operation =~ /^(add|del|confirm-del|undo-add)$/;
+ if ($operation eq 'add') {
+ print STDERR "please supply the new key on STDIN. (I recommend you
+ don't try to do this interactively, but use a pipe)\n";
+ kf_add($gl_user, $keyid, safe_stdin());
+ } elsif ($operation eq 'del') {
+ kf_del($gl_user, $keyid);
+ } elsif ($operation eq 'confirm-del') {
+ die "you dont have any keys marked for deletion\n" unless @marked_for_del;
+ kf_confirm_del($gl_user, $keyid);
+ } elsif ($operation eq 'undo-add') {
+ die "you dont have any keys marked for addition\n" unless @marked_for_add;
+ kf_undo_add($gl_user, $keyid);
+ }
+} elsif ($keytype eq 'del') {
+ # user is using a key that was marked for deletion. The only possible use
+ # for this is that she changed her mind for some reason (maybe she marked
+ # the wrong key for deletion) or is not able to get her client-side sshd
+ # to stop using this key
+ die "valid operations: undo-del\n" unless $operation eq 'undo-del';
+
+ # reinstate the key
+ kf_undo_del($gl_user, $keyid);
+} elsif ($keytype eq 'add') {
+ die "valid operations: confirm-add\n" unless $operation eq 'confirm-add';
+ # user is trying to validate a key that has been previously marked for
+ # addition. This isn't interactive, but it *could* be... if someone asked
+ kf_confirm_add($gl_user, $keyid);
+}
+
+exit;
+
+# ----
+
+# make a temp clone and switch to it
+our $TEMPDIR;
+BEGIN { $TEMPDIR=`mktemp -d -t tmp.XXXXXXXXXX`; }
+END { `/bin/rm -rf $TEMPDIR`; }
+sub cd_temp_clone {
+ chomp($TEMPDIR);
+ hushed_git("clone", "$ENV{GL_REPO_BASE_ABS}/gitolite-admin.git", "$TEMPDIR");
+ chdir($TEMPDIR);
+ my $hostname = `hostname`; chomp($hostname);
+ hushed_git("config", "--get", "user.email") and hushed_git("config", "user.email", $ENV{USER} . "@" . $hostname);
+ hushed_git("config", "--get", "user.name") and hushed_git("config", "user.name", "$ENV{USER} on $hostname");
+}
+
+sub fingerprint {
+ my $fp = `ssh-keygen -l -f $_[0]`;
+ die "does not seem to be a valid pubkey\n" unless $fp =~ /(([0-9a-f]+:)+[0-9a-f]+ )/i;
+ return $1;
+}
+
+sub safe_stdin {
+ # read one line from STDIN
+ my $data;
+ my $ret = read STDIN, $data, 4096;
+ # current pubkeys are approx 400 bytes so we go a little overboard
+ die "could not read pubkey data" . (defined($ret) ? "" : ": $!") . "\n" unless $ret;
+ die "pubkey data seems to have more than one line\n" if $data =~ /\n./;
+ return $data;
+}
+
+sub hushed_git {
+ local(*STDOUT) = \*STDOUT;